- An official website of the United States
- government
-
-
- Here’s how you know
+ An official website of the United States government
+
Here’s how you know
-
+
- Official websites use .gov A .gov website
- belongs to an official government
- organization in the United States.
+ Official websites use .gov A .gov website
+ belongs to an official government organization in the United States.
@@ -99,10 +71,7 @@
/>
- Secure .gov websites use
- HTTPS A
+ Secure .gov websites use HTTPS A
lock ()
- or https:// means
- you’ve safely connected to the .gov
- website. Share sensitive information
- only on official, secure websites.
+ or https:// means you’ve safely connected to the .gov website.
+ Share sensitive information only on official, secure websites.
- JavaScript is required to use this site. In addition,
- please ensure your browser is up-to-date from one of the
- following:
+ JavaScript is required to use this site. In addition, please ensure your browser is up-to-date
+ from one of the following:
diff --git a/frontend-react/vite.config.ts b/frontend-react/vite.config.ts
index 4a136c666e8..c4b32818d54 100644
--- a/frontend-react/vite.config.ts
+++ b/frontend-react/vite.config.ts
@@ -22,15 +22,13 @@ const TRIALFRONTEND_MODES = /^trialfrontend\d+$/;
*/
function createProxyUrl(mode: string) {
if (LOCAL_PROXY_MODES.includes(mode)) return "http://127.0.0.1:7071";
- const subdomain =
- mode === "production" ? "" : mode === "ci" ? "staging" : mode;
+ const subdomain = mode === "production" ? "" : mode === "ci" ? "staging" : mode;
return `https://${subdomain ? subdomain + "." : ""}prime.cdc.gov`;
}
function createBackendUrl(mode: string) {
const port = getPort(mode);
- if (LOCAL_BACKEND_MODES.includes(mode))
- return `http://localhost${port ? ":" + port.toString() : ""}`;
+ if (LOCAL_BACKEND_MODES.includes(mode)) return `http://localhost${port ? ":" + port.toString() : ""}`;
return `https://${mode !== "production" ? (mode.startsWith("trialfrontend") ? "staging" : mode) + "." : ""}prime.cdc.gov`;
}
@@ -78,19 +76,14 @@ export default defineConfig(async ({ mode }) => {
mdx({
mdExtensions: [],
providerImportSource: "@mdx-js/react",
- remarkPlugins: [
- remarkMdxToc,
- remarkFrontmatter,
- remarkMdxFrontmatter,
- ],
+ remarkPlugins: [remarkMdxToc, remarkFrontmatter, remarkMdxFrontmatter],
rehypePlugins: [rehypeSlug],
}),
svgr(),
checker({
typescript: true,
eslint: {
- lintCommand:
- 'eslint "./src/**/*[!.test][!.stories].{ts,tsx}"',
+ lintCommand: 'eslint "./src/**/*[!.test][!.stories].{ts,tsx}"',
},
}),
],
@@ -146,10 +139,7 @@ export default defineConfig(async ({ mode }) => {
// than index
index: resolve(__dirname, "index.html"),
notfound: resolve(__dirname, "404.html"),
- unsupportedBrowser: resolve(
- __dirname,
- "unsupported-browser.html",
- ),
+ unsupportedBrowser: resolve(__dirname, "unsupported-browser.html"),
},
},
},
@@ -166,10 +156,7 @@ export default defineConfig(async ({ mode }) => {
environment: "jsdom",
setupFiles: "./src/setupTests.ts",
globalSetup: "./src/globalSetup.ts",
- include: [
- "./src/**/__tests__/**/*.[jt]s?(x)",
- "./src/**/?(*.)+(spec|test).[jt]s?(x)",
- ],
+ include: ["./src/**/__tests__/**/*.[jt]s?(x)", "./src/**/?(*.)+(spec|test).[jt]s?(x)"],
css: false,
coverage: {
include: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
@@ -187,10 +174,7 @@ export default defineConfig(async ({ mode }) => {
},
resolve: {
alias: {
- "msw/native": resolve(
- __dirname,
- "./node_modules/msw/lib/native/index.mjs",
- ),
+ "msw/native": resolve(__dirname, "./node_modules/msw/lib/native/index.mjs"),
},
},
};
diff --git a/operations/app/terraform/modules/common/sftp/main.tf b/operations/app/terraform/modules/common/sftp/main.tf
index de99a7d770d..c4521b04477 100644
--- a/operations/app/terraform/modules/common/sftp/main.tf
+++ b/operations/app/terraform/modules/common/sftp/main.tf
@@ -28,7 +28,7 @@ resource "azurerm_container_group" "sftp" {
container {
name = "sftp-source"
- image = "atmoz/sftp:latest"
+ image = "ghcr.io/cdcgov/prime-reportstream_sftp:latest"
cpu = var.cpu
cpu_limit = 0
memory = var.memory
diff --git a/operations/app/terraform/modules/container_registry/~outputs.tf b/operations/app/terraform/modules/container_registry/~outputs.tf
index e8db0c48692..a5db627c4f4 100644
--- a/operations/app/terraform/modules/container_registry/~outputs.tf
+++ b/operations/app/terraform/modules/container_registry/~outputs.tf
@@ -11,3 +11,7 @@ output "container_registry_admin_password" {
output "container_registry_id" {
value = azurerm_container_registry.container_registry.id
}
+output "meta" {
+ sensitive = true
+ value = azurerm_container_registry.container_registry
+}
diff --git a/operations/app/terraform/modules/sftp_container/main.tf b/operations/app/terraform/modules/sftp_container/main.tf
index 589cb5fc60d..7fa3e042d31 100644
--- a/operations/app/terraform/modules/sftp_container/main.tf
+++ b/operations/app/terraform/modules/sftp_container/main.tf
@@ -1,23 +1,9 @@
-resource "azurerm_network_profile" "sftp_vnet_network_profile" {
- name = "sftp_vnet_network_profile"
- location = var.location
- resource_group_name = var.resource_group
-
- container_network_interface {
- name = "sftp_container_vnet_network_interface"
- ip_configuration {
- name = "sftp_container_vnet_ip_configuration"
- subnet_id = data.azurerm_subnet.container_subnet.id
- }
- }
-}
-
resource "azurerm_container_group" "sftp_container" {
name = "${var.resource_prefix}-sftpserver"
location = var.location
resource_group_name = var.resource_group
ip_address_type = "Private"
- network_profile_id = azurerm_network_profile.sftp_vnet_network_profile.id
+ subnet_ids = [data.azurerm_subnet.container_subnet.id]
os_type = "Linux"
restart_policy = "Always"
@@ -29,7 +15,7 @@ resource "azurerm_container_group" "sftp_container" {
container {
name = "${var.resource_prefix}-sftpserver"
- image = "atmoz/sftp:alpine"
+ image = "ghcr.io/cdcgov/prime-reportstream_sftp:alpine"
cpu = 1.0
memory = 1.5
@@ -60,8 +46,7 @@ resource "azurerm_container_group" "sftp_container" {
}
depends_on = [
- azurerm_storage_share.sftp_share,
- azurerm_network_profile.sftp_vnet_network_profile
+ azurerm_storage_share.sftp_share
]
}
diff --git a/operations/docker-compose.yml b/operations/docker-compose.yml
index 2363b66da4e..81a9072a8c5 100644
--- a/operations/docker-compose.yml
+++ b/operations/docker-compose.yml
@@ -1,5 +1,3 @@
-version: "3.3"
-
services:
tf-cli:
container_name: terraform_state
diff --git a/operations/slack-boltjs-app b/operations/slack-boltjs-app
index da24bed12bf..f2fdbaeee38 160000
--- a/operations/slack-boltjs-app
+++ b/operations/slack-boltjs-app
@@ -1 +1 @@
-Subproject commit da24bed12bf449394ea84bc266402fb0169be239
+Subproject commit f2fdbaeee3805fec7ed27ea89df236e6b91ce149
diff --git a/prime-router/build.gradle.kts b/prime-router/build.gradle.kts
index a8471c7fda6..63a2d75897a 100644
--- a/prime-router/build.gradle.kts
+++ b/prime-router/build.gradle.kts
@@ -804,7 +804,6 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
implementation("com.microsoft.azure.functions:azure-functions-java-library:3.1.0")
- implementation("com.microsoft.azure:applicationinsights-core:3.5.3")
implementation("com.azure:azure-core:1.49.1")
implementation("com.azure:azure-core-http-netty:1.15.1")
implementation("com.azure:azure-storage-blob:12.26.1") {
@@ -832,7 +831,6 @@ dependencies {
implementation("tech.tablesaw:tablesaw-core:0.43.1")
implementation("com.github.ajalt.clikt:clikt-jvm:4.4.0")
- implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion")
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
implementation("com.github.javafaker:javafaker:1.0.2") {
diff --git a/prime-router/docker-compose.build.yml b/prime-router/docker-compose.build.yml
index fa4bb3563a5..7f81cb53891 100644
--- a/prime-router/docker-compose.build.yml
+++ b/prime-router/docker-compose.build.yml
@@ -1,5 +1,4 @@
# Build environment
-version: "3.3"
services:
builder:
depends_on:
diff --git a/prime-router/docker-compose.postgres.yml b/prime-router/docker-compose.postgres.yml
index 8270036717d..350cb5338af 100644
--- a/prime-router/docker-compose.postgres.yml
+++ b/prime-router/docker-compose.postgres.yml
@@ -1,5 +1,4 @@
# Build environment
-version: "3.3"
services:
postgresql:
build:
@@ -15,4 +14,4 @@ services:
networks:
- prime-router_build
networks:
- prime-router_build:
\ No newline at end of file
+ prime-router_build:
diff --git a/prime-router/docker-compose.yml b/prime-router/docker-compose.yml
index 104b7692871..f82cf32b07c 100644
--- a/prime-router/docker-compose.yml
+++ b/prime-router/docker-compose.yml
@@ -1,5 +1,4 @@
# This docker compose file is intended to set up a developer environment
-version: "3.3"
services:
# This container runs our Azure function code.
prime_dev:
@@ -56,6 +55,7 @@ services:
ports:
- "10000:10000"
- "10001:10001"
+ - "10002:10002"
networks:
- prime-router_build
diff --git a/prime-router/docs/standard-operating-procedures/configuring-filters.md b/prime-router/docs/standard-operating-procedures/configuring-filters.md
new file mode 100644
index 00000000000..a2002e8ef5b
--- /dev/null
+++ b/prime-router/docs/standard-operating-procedures/configuring-filters.md
@@ -0,0 +1,36 @@
+# Configuring Filters
+
+## Frontend User Interface
+
+The admin user interface at https://reportstream.cdc.gov/ allows a PRIME admin to
+manage the settings of an organization, sender and/or receiver. Filters are configured as free text and the input text
+must conform to the expected syntax.
+
+## Command Line Interface
+
+All filters for receivers and organizations can be created/updated/deleted via the command line.
+
+1. create a .yml file containing the updated FHIRPath expressions. Ensure the file begins with “---”. Example:
+
+```yaml
+---
+- name: yoyodyne
+ description: Yoyodyne Propulsion Laboratories, the Future Starts Tomorrow!
+ jurisdiction: FEDERAL
+ receivers:
+ - name: ELR
+ externalName: yoyodyne ELR
+ organizationName: yoyodyne
+ topic: full-elr
+ customerStatus: active
+ jurisdictionalFilter: [ "(Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state.exists() and Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state = 'CA') ]
+```
+
+2. Use the following commands to load the information from the .yml files into the staging database. First obtain a
+ login token for staging
+
+`./prime login -–env staging`
+
+Next update the staging DB
+
+`./prime multiple-settings set –-env staging -–input `
diff --git a/prime-router/docs/universal-pipeline/README.md b/prime-router/docs/universal-pipeline/README.md
index 195ff82e632..2a8e775e2dd 100644
--- a/prime-router/docs/universal-pipeline/README.md
+++ b/prime-router/docs/universal-pipeline/README.md
@@ -18,8 +18,9 @@ In order to handle different data **formats** and **types** in a scalable and ma
```mermaid
flowchart LR;
Receive-->Convert;
- Convert-->Route;
- Route-->Translate;
+ Convert-->Destination Filter;
+ Destination Filter-->Receiver Filter;
+ Receiver Filter-->Translate;
Translate-->Batch;
Batch-->Send;
```
@@ -27,7 +28,8 @@ The sections listed below will aim to describe each step of the pipeline in tech
- [Receive](./receive.md)
- [Convert](./convert.md)
-- [Route](./route.md)
+- [Destination Filter](./destination-filter.md)
+- [Receiver Filter](./receiver-filter.md)
- [Translate](./translate.md)
- [Batch](./batch.md)
- [Send](./send.md)
@@ -73,7 +75,7 @@ val blobInfo = BlobAccess.uploadBody(
bodyBytes,
report.name,
message.blobSubFolderName,
- routeEvent.eventAction
+ nextEvent.eventAction
)
```
@@ -89,7 +91,7 @@ Each pipeline step defined in `FHIRFunctions.kt`, besides [Receive](./receive.md
![azure-queues.png](../assets/azure-queues.png)
-When a function has completed successfully, is will use the `QueueAccess` object to send a message to AQS for the next step to run. For example, the Convert step will schedule the Route step for each new Report it created from the original received Report:
+When a function has completed successfully, is will use the `QueueAccess` object to send a message to AQS for the next step to run. For example, the Convert step will schedule the Destination Filter step for each new Report it created from the original received Report:
```Kotlin
messagesToSend.forEach {
@@ -110,9 +112,10 @@ messagesToSend.add(
)
```
-The three queues specific to the Universal Pipeline are:
+The four queues specific to the Universal Pipeline are:
- elr-fhir-convert
-- elr-fhir-route
+- elr-fhir-destination-filter
+- elr-fhir-receiver-filter
- elr-fhir-translate
The two queues shared with Legacy Pipeline are:
@@ -137,7 +140,7 @@ In addition to the built-in AQS retry mechanism, the unexpected failure will als
> Before continuing with this section, make sure you understand the concept of a [Report and Item](#report-and-item) in ReportStream!
-Generally, each step takes in one Report, performs some operation, and outputs a different Report, referred to as a `Child Report`. In some steps, like the [Convert](convert.md) step, the received Report may be split up into multiple child reports! For the Convert step specifically, this is the case when a Report contains multiple items. The Convert step will split the Report into multiple Reports, so that each Report contains only one item - this is done to prepare for the [Route](route.md) and [Translate](translate.md) steps.
+Generally, each step takes in one Report, performs some operation, and outputs a different Report, referred to as a `Child Report`. In some steps, like the [Convert](convert.md) step, the received Report may be split up into multiple child reports! For the Convert step specifically, this is the case when a Report contains multiple items. The Convert step will split the Report into multiple Reports, so that each Report contains only one item - this is done to prepare for the [Destination Filter](destination-filter.md), [Receiver Filter](receiver-filter.md), and [Translate](translate.md) steps.
![item-lineage.png](../assets/item-lineage.png)
@@ -199,7 +202,7 @@ Step 1: User submits a report via the reports API and gets the following object
Step 2: Some time later, could be right away or could be days from the original submission, the user calls the history endpoint with the id returned from the submission query (`5036f90b-edd5-4a93-9edd-a71d1fa8fba1`) and gets the following object back in the response. The important things to note here are:
- `overallStatus` changed from `Received` to `Waiting to Deliver`. This indicates the report made it all the way from the [Receive](receive.md) step in the pipeline to the [Translate](translate.md) step and is now waiting on the [Batch](batch.md) function to run. Once the Batch and [Send](send.md) steps run, the `overallStatus` will be set to `Delivered`.
-- `destinations` was populated with three receiver objects, indicating the [Route](route.md) step determined the report contained items that matched the initial topic and jurisdiction filters of those receivers. The filters for the *CDC-ELIMS-HL7.CDC-ELIMS-RECEIVER* organization allowed the item in the report through (`itemCount` matches `itemCountBeforeQualityFiltering`) but the filters for *flexion.simulated-lab-2* and *flexion.simulated-lab* filtered out the item (`itemCount` is 0), and the `filteredReportRows` element indicates why. In this case, it was the processing mode filter that determined the item should not go through.
+- `destinations` was populated with three receiver objects, indicating the [Destination Filter](destination-filter.md) step determined the report contained items that matched the initial topic and jurisdiction filters of those receivers. The filters for the *CDC-ELIMS-HL7.CDC-ELIMS-RECEIVER* organization allowed the item in the report through (`itemCount` matches `itemCountBeforeQualityFiltering`) but the filters for *flexion.simulated-lab-2* and *flexion.simulated-lab* filtered out the item (`itemCount` is 0), and the `filteredReportRows` element indicates why. In this case, it was the processing mode filter that determined the item should not go through.
- `destinationCount` is set to `1`, meaning the item in the submitted report passed all the filters for one receiver, in this case *CDC-ELIMS-HL7.CDC-ELIMS-RECEIVER*.
```JSON
{
diff --git a/prime-router/docs/universal-pipeline/destination-filter.md b/prime-router/docs/universal-pipeline/destination-filter.md
new file mode 100644
index 00000000000..fd9162c4d38
--- /dev/null
+++ b/prime-router/docs/universal-pipeline/destination-filter.md
@@ -0,0 +1,197 @@
+# Universal Pipeline Destination Filter Step
+
+## Context
+
+The Destination Filter function’s purpose is to match FHIR bundles with receivers. Each receiver connected with
+ReportStream has unique interests in the data that flows through the pipeline. Destination filtering is designed to find
+the data that may meet those interests (other steps could still filter out the data).
+
+The function follows the [Convert](convert.md) function. At this point all data will be in FHIR format. These messages
+are passed to the FHIR Destination Filter which first decodes a FHIR Bundle. It then evaluates jurisdiction filters for
+all active receivers with a matching topic to find receivers that could accept the bundle. The message is then passed to
+the [Receiver Filter](receiver-filter.md) function to evaluate remaining receiver-specific filters.
+
+### Topic
+
+A Topic must be set for all senders and receivers. The choice of topic determines which pipeline is used (Universal or
+Legacy) and will affect how routing takes place. The destination filtering step will start by limiting available
+receivers to only those with a topic matching the sender topic.
+
+Topics include:
+
+
+
+
Topic Name
+
+
Pipeline
+
+
Description
+
+
+
+
full-elr
+
+
Universal
+
+
General use for test result reporting
+
+
+
+
etor-ti
+
+
Universal
+
+
ETOR project with Flexion’s Trusted Intermediary
+
+
+
+
elr-elims
+
+
Universal
+
+
ELR project with ELIMS
+
+
+
+
+### FHIRPath for Routing
+
+FHIRPath is used to build filter expressions. See FHIRPath documentation
+in [fhir-functions.md](https://github.com/CDCgov/prime-reportstream/blob/d43ab6297a44a4ef2a0fef8d467e79cfcc154f33/prime-router/docs/getting-started/fhir-functions.md)
+
+The table below demonstrates a few filter functions and their FHIRPath equivalent.
+
+
+
%orderingFacilityState = "CO" or \
+Bundle.entry.resource.ofType(Patient).address.state = "CO"
+
+
+
+
+## Filtering
+
+### Purpose
+
+Filter configuration is a part of the settings for a specific organization and/or receiver. There are five main filter
+groups*: Jurisdictional, Quality, Routing, Processing Mode Code, and Condition. Only the Jurisdictional group is
+evaluated during the `Destination Filter Function`. The filter groups are used to organize the filters and make it
+easier to report the filter results to a user, but the functionality is the same for all the filters. All filters take
+an array of expressions where all expressions must evaluate to true for the filter group to evaluate to true.
+
+_*Filter groups may have been referred to as filter types in the past._
+
+
+### Configuring Filters
+
+Filters are configured in the settings stored in the database. See the relevant [document](../standard-operating-procedures/configuring-filters.md) for more info.
+
+### Jurisdictional Filter
+
+Identifies data that falls within a receiver’s jurisdiction as most ReportStream organizations are geographic entities
+(e.g. patient state is CO). Note that the non-matching result of the jurisdictional filter is not reported to users via
+the submission history API as this filter is just to decide to which receiver data needs to go.
+
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti, elr-elims
+
+
+
+
Operation
+
+
Expressions are evaluated with AND operation
+
+
+
+
Default
+
+
Allow None: Allowing none is a safeguard as jurisdictional filtering keeps data from one jurisdiction being reported to another. This must be overwritten with a custom filter for each receiver
+
+
+
+
+## Storage
+
+The `Destination Filter Function` retrieves messages from the `pdhprodstorageaccount` Azure Storage Account. Within that
+storage account there is a blob container named `reports` containing folders for use by the Universal Pipeline.
+The `Convert Function` places all messages into the `destination-filter` folder for retrieval by the
+`Destination Filter Function`. Messages that match an active receiver's topic and jurisdiction are then be placed in the
+`receiver-filter` folder for future retrieval by the `Receiver Filter Function`. Messages within the `receiver-filter`
+folder are saved to sub-folders equaling the name of the sender.
+
+## Logging
+
+### Action Logs
+
+This filter does not log any actions.
+
+### Console Logging
+
+This filter will log messages to the console when:
+- First initialized
+- Downloading the bundle (includes duration)
+- Routing a report to receivers (includes number of receivers)
+- No receivers are valid for a report
+
+## Events
+
+This step emits one of two events below _once_ each time it runs.
+
+| Event | Trigger |
+|-------------------------------------------------------------------------------|------------------------------------------------------------------|
+| [ReportAcceptedEvent](../observability/azure-events.md#reportacceptedevent) | when a report is received by this step (precludes all filtering) |
+| [ReportNotRoutedEvent](../observability/azure-events.md#reportnotroutedevent) | when a report is not valid for any receivers |
+
+## Retries
+
+There is no custom retry strategy for this step. If an uncaught exception occurs during this step, the message is
+re-queued up to five times before being placed in the poison queue.
diff --git a/prime-router/docs/universal-pipeline/receiver-filter.md b/prime-router/docs/universal-pipeline/receiver-filter.md
new file mode 100644
index 00000000000..3c0de1e403d
--- /dev/null
+++ b/prime-router/docs/universal-pipeline/receiver-filter.md
@@ -0,0 +1,303 @@
+# Universal Pipeline Receiver Filter Step
+
+## Context
+
+The Destination Filter function’s evaluates a receiver's filters on a bundle and determines if it should be sent. This
+may include pruning of observations in the bundle. Each receiver connected with ReportStream has unique interests in
+the data that flows through the pipeline. This step is designed to find the data that meet those interests.
+
+The function follows the [Destination Filter](destination-filter.md) function. These messages are passed to the FHIRi
+Receiver Filter which first decodes a FHIR Bundle. Then, quality, processing, routing, and condition filters are
+evaluated to determine if the bundle should be sent and prune it of unneeded data. If the message passes, it is sent
+to the [Translate](translate.md) function where receiver specific work is done to prepare for batching and sending.
+
+### Pruning
+
+During evaluation of the condition filters in the `Receiver Filter Function`, bundle observations that are not of
+interest to the receiver are pruned, or removed. If there are no observations of interest to the receiver, the bundle
+is not sent.
+
+### FHIRPath for Routing
+
+FHIRPath is used to build filter expressions. See FHIRPath documentation
+in [fhir-functions.md](https://github.com/CDCgov/prime-reportstream/blob/d43ab6297a44a4ef2a0fef8d467e79cfcc154f33/prime-router/docs/getting-started/fhir-functions.md)
+
+The table below demonstrates a few filter functions and their FHIRPath equivalent.
+
+
+
%orderingFacilityState = "CO" or \
+Bundle.entry.resource.ofType(Patient).address.state = "CO"
+
+
+
+
+## Filtering
+
+### Purpose
+
+Filter configuration is a part of the settings for a specific organization and/or receiver. There are five main filter
+groups*: Jurisdictional, Quality, Routing, Processing Mode Code, and Condition. The Quality, Routing, Processing Mode
+Code, and Condition groups are evaluated during the `Receiver Filter Function`. These filter groups are used to organize
+the filters and make it easier to report the filter results to a user, but the functionality is the same for all the
+filters. All filters take an array of expressions where all must evaluate to true for the group to be true.
+
+_*Filter groups may have been referred to as filter types in the past._
+
+### Configuring Filters
+
+Filters are configured in the settings stored in the database. See the relevant [document](../standard-operating-procedures/configuring-filters.md) for more info.
+
+### Quality Filter
+
+Filter out any data that does not meet the specified minimum requirements (e.g. must have patient last name)
+
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti, elr-elims
+
+
+
+
Operation
+
+
Expressions are evaluated as an AND operation
+
+
+
+
Default
+
+
See code block below
+
+
+
+
+```kotlin
+/**
+ * Default Rules:
+ * Must have message ID, patient last name, patient first name, DOB, specimen type
+ * At least one of patient street, patient zip code, patient phone number, patient email
+ * At least one of order test date, specimen collection date/time, test result date
+ */
+val qualityFilterDefault: ReportStreamFilter = listOf(
+ "Bundle.entry.resource.ofType(MessageHeader).id.exists()",
+ "Bundle.entry.resource.ofType(Patient).name.family.exists()",
+ "Bundle.entry.resource.ofType(Patient).name.given.count() > 0",
+ "Bundle.entry.resource.ofType(Patient).birthDate.exists()",
+ "Bundle.entry.resource.ofType(Specimen).type.exists()",
+ "(Bundle.entry.resource.ofType(Patient).address.line.exists() or " +
+ "Bundle.entry.resource.ofType(Patient).address.postalCode.exists() or " +
+ "Bundle.entry.resource.ofType(Patient).telecom.exists())",
+ "(" +
+ "(Bundle.entry.resource.ofType(Specimen).collection.collectedPeriod.exists() or " +
+ "Bundle.entry.resource.ofType(Specimen).collection.collected.exists()" +
+ ") or " +
+ "Bundle.entry.resource.ofType(ServiceRequest).occurrence.exists() or " +
+ "Bundle.entry.resource.ofType(Observation).effective.exists())"
+ )
+```
+
+### Routing Filter
+
+Generic filtering that does not concern data quality or condition (e.g. test result is positive)
+
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti, elr-elims
+
+
+
+
Operation
+
+
Expressions are evaluated with AND operation
+
+
+
+
Default
+
+
Allow All: The door is open. No filter is in place
+
+
+
+
+### Processing Mode Code Filter
+
+The processing mode of the data indicates the sender’s intended context for the data. Options for this field are found
+here [CodeSystem: processingId](https://terminology.hl7.org/5.2.0/CodeSystem-v2-0103.html). The intention is to ensure
+the sender and receiver operate with the same data content context. Test data should only be accepted by test receivers.
+Production data should only be accepted by production receivers.
+
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti
+
+
+
+
Operation
+
+
Expressions are evaluated with AND operation
+
+
+
+
Default
+
+
See code block below
+
+
+
+
+```kotlin
+/**
+ * Default Rule:
+ * Must have a processing mode id of 'P'
+ */
+val processingModeFilterDefault: ReportStreamFilter = listOf(
+ "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(system = 'http://terminology.hl7.org/CodeSystem/v2-0103').code = 'P'"
+ )
+```
+
+### Condition Filter
+
+Filter data based on the test identifiers using FHIR expressions. A receiver expecting flu results should only accept
+tests for flu. If the message contains multiple observations, some that pass the condition filter and others that do
+not, the condition filter will pass if any bundle observations are of interest to the receiver.
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti, elr-elims
+
+
+
+
Operation
+
+
Expressions are evaluated with OR operation
+
+
+
+
Default
+
+
Allow All: The door is open. No filter is in place
+
+
+
+
+### Mapped Condition Filter
+
+Filter data based on the condition stamp added by ReportStream. A receiver expecting flu results should only accept
+tests for flu. If the message contains multiple observations, some that pass others that do not, the mapped condition
+filter will pass if the bundle contains any mapped conditions of interest to the receiver.
+
+
+
+
Topic
+
+
Applies to following topics: full-elr, etor-ti, elr-elims
+
+
+
+
Operation
+
+
Expressions are evaluated with OR operation
+
+
+
+
Default
+
+
Allow All: The door is open. No filter is in place
+
+
+
+
+## Storage
+
+The `Receiver Filter Function` retrieves messages from the `pdhprodstorageaccount` Azure Storage Account. Within that
+storage account there is a blob container named `reports` containing folders for use by the Universal Pipeline. The
+`Destination Filter Function` places all messages into the `receiver-filter` folder for retrieval by the
+`Receiver Filter Function`. Those messages that match a receiver's filtering will then be placed in the `translate`
+folder for future retrieval by the `Translate Function`. Messages within the `receiver-filter` folder are saved to
+sub-folders equaling the name of the sender.
+
+## Logging
+
+### Action Logs
+
+This filter will log actions when:
+- Filtering observations due to any filter (incl: filter text, filter type, org details, index)
+
+### Console Logging
+
+This filter will log messages to the console when:
+- First initialized
+- Downloading the bundle (includes duration)
+- Observations have been filtered from a bundle
+- Receiver filters passed and a pruned bundle is ready
+- Receiver filters failed and lineage will be terminated
+
+## Events
+
+This step emits one of the below events _per receiver_ each time it runs.
+
+| Event | Trigger |
+|-----------------------------------------------------------------------------------------|------------------------------------------------------------|
+| [ReportRouteEvent](../observability/azure-events.md#reportrouteevent) | When a report is routed to a receiver (all filters passed) |
+| [ReceiverFilterFailedEvent](../observability/azure-events.md#receiverfilterfailedevent) | When a report fails receiver filters |
+
+## Retries
+
+There is no custom retry strategy for this step. If an error occurs during this step, the message is re-queued up to
+five times before being placed in the poison queue.
\ No newline at end of file
diff --git a/prime-router/docs/universal-pipeline/route.md b/prime-router/docs/universal-pipeline/route.md
index ae939514793..b986eeb3ba7 100644
--- a/prime-router/docs/universal-pipeline/route.md
+++ b/prime-router/docs/universal-pipeline/route.md
@@ -1,4 +1,4 @@
-# Universal Pipeline Route Step
+# Deprecated Universal Pipeline Route Step
## Context
diff --git a/prime-router/settings/STLTs/AL/al-phl.yml b/prime-router/settings/STLTs/AL/al-phl.yml
index d0caec8e4e2..c5ac1c61408 100644
--- a/prime-router/settings/STLTs/AL/al-phl.yml
+++ b/prime-router/settings/STLTs/AL/al-phl.yml
@@ -43,7 +43,7 @@
maxReportCount: 100
translation:
type: "HL7"
- schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml"
+ schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml"
useBatchHeaders: false
receivingApplicationName: "AL-PHL"
receivingFacilityName: "AL"
diff --git a/prime-router/settings/STLTs/Flexion/flexion.yml b/prime-router/settings/STLTs/Flexion/flexion.yml
index 15b9e4ca767..79751aed681 100644
--- a/prime-router/settings/STLTs/Flexion/flexion.yml
+++ b/prime-router/settings/STLTs/Flexion/flexion.yml
@@ -32,7 +32,7 @@
topic: "etor-ti"
customerStatus: "active"
format: "FHIR"
- schemaName: "classpath:/metadata/fhir_transforms/senders/Flexion/TILabOrder.yml"
+ schemaName: "classpath:/metadata/fhir_transforms/senders/Flexion/etor-sender-transform.yml"
# *** Settings used for testing below ***
# Simulated Hospital Sender - Sending HL7 orders
- name: "simulated-hospital"
@@ -164,7 +164,7 @@
initialTime: "00:00"
translation:
type: "HL7"
- schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml"
+ schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml"
useTestProcessingMode: false
useBatchHeaders: false
transport:
diff --git a/prime-router/settings/STLTs/IL/il-phd.yml b/prime-router/settings/STLTs/IL/il-phd.yml
new file mode 100644
index 00000000000..19422b9b869
--- /dev/null
+++ b/prime-router/settings/STLTs/IL/il-phd.yml
@@ -0,0 +1,105 @@
+# To submit this to localhost, run:
+#
+# ./prime multiple-settings set --input ./settings/STLTs/IL/il-phd.yml
+#
+---
+- name: "il-phd"
+ description: "Illinois Public Health Department"
+ jurisdiction: "STATE"
+ stateCode: "IL"
+ countyName: null
+ filters: []
+ senders: []
+ receivers:
+ - name: "full-elr"
+ organizationName: "il-phd"
+ topic: "full-elr"
+ customerStatus: "active"
+ translation: !
+ schemaName: "azure:/hl7_mapping/receivers/STLTs/IL/IL-receiver-transform.yml"
+ useTestProcessingMode: false
+ useBatchHeaders: true
+ receivingApplicationName: null
+ receivingApplicationOID: null
+ receivingFacilityName: null
+ receivingFacilityOID: null
+ messageProfileId: null
+ replaceValue: {}
+ replaceValueAwithB: {}
+ reportingFacilityName: "CDC PRIME"
+ reportingFacilityId: "11D2030855"
+ reportingFacilityIdType: null
+ suppressQstForAoe: false
+ suppressHl7Fields: null
+ suppressAoe: true
+ defaultAoeToUnknown: false
+ replaceUnicodeWithAscii: false
+ useBlankInsteadOfUnknown: null
+ truncateHDNamespaceIds: true
+ truncateHl7Fields: "OBX-23-1, ORC-21-1"
+ usePid14ForPatientEmail: false
+ convertTimestampToDateTime: null
+ cliaForOutOfStateTesting: null
+ cliaForSender: {}
+ phoneNumberFormatting: "STANDARD"
+ suppressNonNPI: true
+ processingModeCode: null
+ replaceDiiWithOid: null
+ applyOTCDefault: false
+ useOrderingFacilityName: "STANDARD"
+ valueSetOverrides: {}
+ nameFormat: "STANDARD"
+ receivingOrganization: null
+ convertPositiveDateTimeOffsetToNegative: true
+ stripInvalidCharsRegex: null
+ convertDateTimesToReceiverLocalTime: false
+ useHighPrecisionHeaderDateTimeFormat: false
+ type: "HL7"
+ truncationConfig:
+ truncateHDNamespaceIds: true
+ truncateHl7Fields:
+ - "OBX-23-1"
+ - "ORC-21-1"
+ customLengthHl7Fields: {}
+ jurisdictionalFilter:
+ - "(Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state = 'IL') or (Bundle.entry.resource.ofType(Patient).address.state = 'IL')"
+ qualityFilter:
+ - "((Bundle.entry.resource.ofType(Specimen).collection.collectedPeriod.exists() or Bundle.entry.resource.ofType(Specimen).collection.collected.exists()) or Bundle.entry.resource.ofType(serviceRequest).occurrence.exists() or Bundle.entry.resource.ofType(Observation).effective.exists())"
+ - "(Bundle.entry.resource.ofType(Patient).address.line.exists() or Bundle.entry.resource.ofType(Patient).address.postalCode.exists() or Bundle.entry.resource.ofType(Patient).telecom.exists())"
+ - "Bundle.entry.resource.ofType(MessageHeader).id.exists()"
+ - "Bundle.entry.resource.ofType(Patient).birthDate.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.family.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.given.count() > 0"
+ - "Bundle.entry.resource.ofType(Specimen).type.exists()"
+ routingFilter:
+ # - "matches(test_result_status, F, C, P)"
+ - "Bundle.entry.resource.ofType(DiagnosticReport).where(status in 'final'|'corrected'|'preliminary'|'amended').exists()"
+ processingModeFilter: []
+ reverseTheQualityFilter: false
+ conditionFilter:
+ # Accept COVID only
+ - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('840539006')).exists())"
+# - "matches(test_type, Mol, Antigen, Breath)"
+ mappedConditionFilter: []
+ deidentify: false
+ deidentifiedValue: ""
+ timing:
+ operation: "MERGE"
+ numberPerDay: 24
+ initialTime: "01:15"
+ timeZone: "EASTERN"
+ maxReportCount: 100
+ whenEmpty:
+ action: "NONE"
+ onlyOncePerDay: false
+ description: ""
+ transport: !
+ host: "sftp"
+ port: "22"
+ filePath: "./upload"
+ credentialName: DEFAULT-SFTP
+ type: "SFTP"
+ externalName: "Primary Feed"
+ enrichmentSchemaNames: []
+ timeZone: null
+ dateTimeFormat: "OFFSET"
\ No newline at end of file
diff --git a/prime-router/settings/STLTs/LA/la-ochsner.yml b/prime-router/settings/STLTs/LA/la-ochsner.yml
index cb05a305d22..f9b3760c68b 100644
--- a/prime-router/settings/STLTs/LA/la-ochsner.yml
+++ b/prime-router/settings/STLTs/LA/la-ochsner.yml
@@ -36,7 +36,8 @@
jurisdictionalFilter:
- "Bundle.entry.resource.ofType(MessageHeader).event.code = 'R01'" # ORU_R01
- "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(system = 'http://localcodes.org/ETOR').code = 'ETOR'" # required to avoid looping issue with TI
- # Pending: add filter(s) for routing
+ # Pending: add correct filter(s) for routing
+ - "Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where(value = 'la-ochsner-simulated-lab-id').exists()"
qualityFilter:
- "true"
timing:
diff --git a/prime-router/settings/STLTs/LA/la-phl.yml b/prime-router/settings/STLTs/LA/la-phl.yml
index 56eeef65ec3..b51f111570a 100644
--- a/prime-router/settings/STLTs/LA/la-phl.yml
+++ b/prime-router/settings/STLTs/LA/la-phl.yml
@@ -43,7 +43,7 @@
maxReportCount: 100
translation:
type: "HL7"
- schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml"
+ schemaName: "classpath:/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml"
useBatchHeaders: false
receivingApplicationName: "LA-PHL"
receivingFacilityName: "LA"
diff --git a/prime-router/settings/STLTs/MS/ms-doh.yml b/prime-router/settings/STLTs/MS/ms-doh.yml
new file mode 100644
index 00000000000..6f16f487b05
--- /dev/null
+++ b/prime-router/settings/STLTs/MS/ms-doh.yml
@@ -0,0 +1,102 @@
+# To submit this to localhost, run:
+#
+# ./prime multiple-settings set --input ./settings/STLTs/MS/ms-doh.yml
+---
+- name: "ms-doh"
+ description: "Mississippi Department of Health"
+ jurisdiction: "STATE"
+ stateCode: "MS"
+ countyName: null
+ filters: null
+ featureFlags: null
+ keys: null
+ senders: []
+ receivers:
+ - name: "full-elr"
+ organizationName: "ms-doh"
+ topic: "full-elr"
+ customerStatus: "active"
+ translation: !
+ schemaName: "azure:/hl7_mapping/receivers/STLTs/MS/MS-receiver-transform.yml"
+ useTestProcessingMode: false
+ useBatchHeaders: true
+ receivingApplicationName: "MSDH-ELR"
+ receivingApplicationOID: null
+ receivingFacilityName: "MSDOH"
+ receivingFacilityOID: null
+ messageProfileId: null
+ replaceValue: {}
+ replaceValueAwithB: {}
+ reportingFacilityName: null
+ reportingFacilityId: null
+ reportingFacilityIdType: null
+ suppressQstForAoe: false
+ suppressHl7Fields: null
+ suppressAoe: false
+ defaultAoeToUnknown: false
+ replaceUnicodeWithAscii: false
+ useBlankInsteadOfUnknown: null
+ truncateHDNamespaceIds: false
+ truncateHl7Fields: null
+ usePid14ForPatientEmail: true
+ convertTimestampToDateTime: null
+ cliaForOutOfStateTesting: null
+ cliaForSender: {}
+ phoneNumberFormatting: "STANDARD"
+ suppressNonNPI: false
+ processingModeCode: null
+ replaceDiiWithOid: null
+ applyOTCDefault: false
+ useOrderingFacilityName: "STANDARD"
+ valueSetOverrides: {}
+ nameFormat: "STANDARD"
+ receivingOrganization: null
+ convertPositiveDateTimeOffsetToNegative: false
+ stripInvalidCharsRegex: null
+ convertDateTimesToReceiverLocalTime: false
+ useHighPrecisionHeaderDateTimeFormat: false
+ type: "HL7"
+ truncationConfig:
+ truncateHDNamespaceIds: false
+ truncateHl7Fields: []
+ customLengthHl7Fields: {}
+ jurisdictionalFilter:
+ - "(Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state = 'MS') or (Bundle.entry.resource.ofType(Patient).address.state = 'MS')"
+ qualityFilter:
+ - "((Bundle.entry.resource.ofType(Specimen).collection.collectedPeriod.exists() or Bundle.entry.resource.ofType(Specimen).collection.collected.exists()) or Bundle.entry.resource.ofType(serviceRequest).occurrence.exists() or Bundle.entry.resource.ofType(Observation).effective.exists())"
+ - "(Bundle.entry.resource.ofType(Patient).address.line.exists() or Bundle.entry.resource.ofType(Patient).address.postalCode.exists() or Bundle.entry.resource.ofType(Patient).telecom.exists())"
+ - "Bundle.entry.resource.ofType(MessageHeader).id.exists()"
+ - "Bundle.entry.resource.ofType(Patient).birthDate.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.family.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.given.count() > 0"
+ - "Bundle.entry.resource.ofType(Specimen).type.exists()"
+ routingFilter: []
+ processingModeFilter: []
+ reverseTheQualityFilter: false
+ conditionFilter:
+ # HIV only
+ - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('86406008')).exists())"
+ mappedConditionFilter: []
+ deidentify: false
+ deidentifiedValue: ""
+ timing:
+ operation: "MERGE"
+ numberPerDay: 1
+ initialTime: "09:50"
+ timeZone: "EASTERN"
+ maxReportCount: 100
+ whenEmpty:
+ action: "NONE"
+ onlyOncePerDay: false
+ description: ""
+ transport: !
+ host: "sftp"
+ port: "22"
+ filePath: "./upload"
+ credentialName: DEFAULT-SFTP
+ type: "SFTP"
+ externalName: null
+ enrichmentSchemaNames:
+ - "classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml"
+ timeZone: null
+ dateTimeFormat: "OFFSET"
\ No newline at end of file
diff --git a/prime-router/settings/STLTs/NM/nm-doh.yml b/prime-router/settings/STLTs/NM/nm-doh.yml
new file mode 100644
index 00000000000..0fe6374ae6d
--- /dev/null
+++ b/prime-router/settings/STLTs/NM/nm-doh.yml
@@ -0,0 +1,98 @@
+- name: "nm-doh"
+ description: "New Mexico Department of Health"
+ jurisdiction: "STATE"
+ stateCode: "NM"
+ countyName: null
+ filters: null
+ featureFlags: null
+ keys: null
+ senders: []
+ receivers:
+ - name: "full-elr"
+ organizationName: "nm-doh"
+ topic: "full-elr"
+ customerStatus: "active"
+ translation:
+ schemaName: "azure:/metadata/hl7_mapping/receivers/STLTs/NM/NM-receiver-transform.yml"
+ useTestProcessingMode: false
+ useBatchHeaders: true
+ receivingApplicationName: "NMDOH^2.16.840.1.113883.3.5364^ISO"
+ receivingApplicationOID: "2.16.840.1.113883.3.5364"
+ receivingFacilityName: "NMDOH^2.16.840.1.113883.3.5364^ISO"
+ receivingFacilityOID: "2.16.840.1.113883.3.5364"
+ messageProfileId: null
+ replaceValue: {}
+ replaceValueAwithB: null
+ reportingFacilityName: null
+ reportingFacilityId: null
+ reportingFacilityIdType: null
+ suppressQstForAoe: false
+ suppressHl7Fields: "OBX-17-1"
+ suppressAoe: false
+ defaultAoeToUnknown: false
+ replaceUnicodeWithAscii: false
+ useBlankInsteadOfUnknown: "patient_race"
+ truncateHDNamespaceIds: true
+ truncateHl7Fields: null
+ usePid14ForPatientEmail: false
+ convertTimestampToDateTime: null
+ cliaForOutOfStateTesting: null
+ cliaForSender: {}
+ phoneNumberFormatting: "STANDARD"
+ suppressNonNPI: false
+ processingModeCode: "P"
+ replaceDiiWithOid: null
+ applyOTCDefault: false
+ useOrderingFacilityName: "STANDARD"
+ valueSetOverrides: {}
+ nameFormat: "APHL_LIGHT"
+ receivingOrganization: "elr"
+ convertPositiveDateTimeOffsetToNegative: false
+ stripInvalidCharsRegex: null
+ convertDateTimesToReceiverLocalTime: false
+ useHighPrecisionHeaderDateTimeFormat: false
+ type: "HL7"
+ truncationConfig:
+ truncateHDNamespaceIds: true
+ truncateHl7Fields: []
+ customLengthHl7Fields: {}
+ jurisdictionalFilter:
+ - "(Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state = 'NM') or (Bundle.entry.resource.ofType(Patient).address.state = 'NM')"
+ qualityFilter:
+ - "Bundle.identifier.value.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.family.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.given.count() > 0"
+ - "Bundle.entry.resource.ofType(Patient).birthDate.exists()"
+ - "Bundle.entry.resource.ofType(Specimen).where(type.empty()).count() = 0"
+ - "Bundle.entry.resource.ofType(Patient).where(address.line[0].empty() and address.postalCode.empty() and telecom.where(system = 'phone').value.empty() and telecom.where(system = 'email').value.empty()).count() = 0"
+ - "((Bundle.entry.resource.ofType(Specimen).collection.collectedPeriod.exists() or Bundle.entry.resource.ofType(Specimen).collection.collected.exists())
+ or (Bundle.entry.resource.ofType(ServiceRequest).occurrence.exists() or Bundle.entry.resource.ofType(Observation).effective.exists()))"
+ routingFilter: [ ]
+ processingModeFilter: []
+ reverseTheQualityFilter: false
+ conditionFilter:
+ # Accept COVID only
+ - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('840539006')).exists())"
+ mappedConditionFilter: []
+ deidentify: false
+ deidentifiedValue: ""
+ timing:
+ operation: "MERGE"
+ numberPerDay: 12
+ initialTime: "01:25"
+ timeZone: "EASTERN"
+ maxReportCount: 100
+ whenEmpty:
+ action: "NONE"
+ onlyOncePerDay: false
+ description: ""
+ transport:
+ type: SFTP
+ host: sftp
+ port: 22
+ filePath: ./upload
+ credentialName: DEFAULT-SFTP
+ externalName: null
+ enrichmentSchemaNames: []
+ timeZone: null
+ dateTimeFormat: "OFFSET"
\ No newline at end of file
diff --git a/prime-router/settings/STLTs/OK/ok-phd.yml b/prime-router/settings/STLTs/OK/ok-phd.yml
index a667af2698d..1c7c78a8ff5 100644
--- a/prime-router/settings/STLTs/OK/ok-phd.yml
+++ b/prime-router/settings/STLTs/OK/ok-phd.yml
@@ -13,7 +13,7 @@
topic: "full-elr"
customerStatus: "active"
translation:
- schemaName: "azure:/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml"
+ schemaName: "azure:/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml"
useTestProcessingMode: false
useBatchHeaders: true
receivingApplicationName: null
@@ -70,8 +70,10 @@
processingModeFilter: []
reverseTheQualityFilter: false
conditionFilter:
- # Accept COVID only
- - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('840539006')).exists())"
+ # Accept following condition_code (see: observation-mapping.csv for more detail)
+ # COVID: 840539006 (OK needs both '+/-'), MPOX: 414015000 (OK needs '+' only)
+ # Note: Not support Mpox for now.
+ - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('840539006')).exists())"
mappedConditionFilter: []
deidentify: false
deidentifiedValue: ""
diff --git a/prime-router/settings/STLTs/Oracle/oracle-rln.yml b/prime-router/settings/STLTs/Oracle/oracle-rln.yml
index ece7a3685d5..150e2a1126b 100644
--- a/prime-router/settings/STLTs/Oracle/oracle-rln.yml
+++ b/prime-router/settings/STLTs/Oracle/oracle-rln.yml
@@ -35,7 +35,8 @@
jurisdictionalFilter:
- "Bundle.entry.resource.ofType(MessageHeader).event.code = 'R01'" # ORU_R01
- "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(system = 'http://localcodes.org/ETOR').code = 'ETOR'" # required to avoid looping issue with TI
- # Pending: add filter(s) for routing
+ # Pending: add correct filter(s) for routing
+ - "Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where(value = 'oracle-rln-simulated-lab-id').exists()"
qualityFilter:
- "true"
timing:
diff --git a/prime-router/settings/STLTs/VT/vt-doh.yml b/prime-router/settings/STLTs/VT/vt-doh.yml
index fd1f6baf5ea..123be6f9db3 100644
--- a/prime-router/settings/STLTs/VT/vt-doh.yml
+++ b/prime-router/settings/STLTs/VT/vt-doh.yml
@@ -97,7 +97,7 @@
filePath: ./upload
credentialName: DEFAULT-SFTP
externalName: "VT ELR Primary"
- enrichmentSchemaNames: ["classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml"]
+ enrichmentSchemaNames: ["classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml", "classpath:/metadata/fhir_transforms/common/suppress-non-npi.yml"]
timeZone: null
dateTimeFormat: "OFFSET"
- name: "full-elr-secondary"
@@ -189,6 +189,6 @@
filePath: ./upload
credentialName: DEFAULT-SFTP
externalName: "VT ELR Primary"
- enrichmentSchemaNames: ["classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml"]
+ enrichmentSchemaNames: ["classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml", "classpath:/metadata/fhir_transforms/common/suppress-non-npi.yml"]
timeZone: null
dateTimeFormat: "OFFSET"
diff --git a/prime-router/src/main/kotlin/ReportStreamFilter.kt b/prime-router/src/main/kotlin/ReportStreamFilter.kt
index a5be2004f44..ed18b910f88 100644
--- a/prime-router/src/main/kotlin/ReportStreamFilter.kt
+++ b/prime-router/src/main/kotlin/ReportStreamFilter.kt
@@ -20,8 +20,11 @@ fun ReportStreamConditionFilter.codes(): List = this.flatMap { it.codes(
JsonSubTypes.Type(FHIRExpressionConditionFilter::class, name = "fhirExpression"),
)
abstract class ConditionFilter(val value: String) {
- // TODO: change to .filter() or .passes() method and combine with legacy filter
abstract fun codes(): List
+
+ override fun toString(): String {
+ return value
+ }
}
class CodeStringConditionFilter(value: String) : ConditionFilter(value) {
diff --git a/prime-router/src/main/kotlin/azure/Event.kt b/prime-router/src/main/kotlin/azure/Event.kt
index df53733eb7e..6d326507358 100644
--- a/prime-router/src/main/kotlin/azure/Event.kt
+++ b/prime-router/src/main/kotlin/azure/Event.kt
@@ -30,7 +30,7 @@ abstract class Event(val eventAction: EventAction, val at: OffsetDateTime?) {
RECEIVER_FILTER,
RECEIVE,
CONVERT, // for universal pipeline converting to FHIR
- ROUTE, // calculate routing for a submission
+ ROUTE, // Deprecated (has become DESTINATION_FILTER->RECEIVER_FILTER)
TRANSLATE,
BATCH,
SEND,
diff --git a/prime-router/src/main/kotlin/azure/observability/event/AzureEventService.kt b/prime-router/src/main/kotlin/azure/observability/event/AzureEventService.kt
index 357fdb8c8ad..22801ac6928 100644
--- a/prime-router/src/main/kotlin/azure/observability/event/AzureEventService.kt
+++ b/prime-router/src/main/kotlin/azure/observability/event/AzureEventService.kt
@@ -30,4 +30,21 @@ class AzureEventServiceImpl(
logger.debug("Sending event of type $name to Azure AppInsights")
telemetryClient.trackEvent(name, event.serialize(), emptyMap())
}
+}
+
+/**
+ * Local storage of azure events (only used for testing)
+ */
+class LocalAzureEventServiceImpl(
+ val events: MutableList = mutableListOf(),
+) : AzureEventService, Logging {
+
+ /**
+ * Send event to Azure AppInsights using the Azure TelemetryClient
+ */
+ override fun trackEvent(event: AzureCustomEvent) {
+ val name = event.javaClass.simpleName
+ logger.debug("Recording'$name' event in memory.")
+ events.add(event)
+ }
}
\ No newline at end of file
diff --git a/prime-router/src/main/kotlin/fhirengine/azure/FHIRFunctions.kt b/prime-router/src/main/kotlin/fhirengine/azure/FHIRFunctions.kt
index d8ba51bb416..b58eda44f50 100644
--- a/prime-router/src/main/kotlin/fhirengine/azure/FHIRFunctions.kt
+++ b/prime-router/src/main/kotlin/fhirengine/azure/FHIRFunctions.kt
@@ -65,12 +65,13 @@ class FHIRFunctions(
val messagesToDispatch = runFhirEngine(message, dequeueCount, fhirEngine, actionHistory)
messagesToDispatch.forEach {
queueAccess.sendMessage(
- elrRoutingQueueName,
+ elrDestinationFilterQueueName,
it.serialize()
)
}
}
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
/**
* An azure function for routing full-ELR FHIR data.
*/
@@ -85,6 +86,7 @@ class FHIRFunctions(
doRoute(message, dequeueCount, FHIRRouter())
}
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
/**
* Functionality separated from azure function call so a mocked fhirEngine can be passed in for testing.
* Reads the [message] passed in and processes it using the appropriate [fhirEngine]. If there is an error
@@ -130,7 +132,7 @@ class FHIRFunctions(
message: String,
dequeueCount: Int,
fhirEngine: FHIRDestinationFilter,
- actionHistory: ActionHistory = ActionHistory(TaskAction.route),
+ actionHistory: ActionHistory = ActionHistory(TaskAction.destination_filter),
) {
val messagesToDispatch = runFhirEngine(message, dequeueCount, fhirEngine, actionHistory)
@@ -166,7 +168,7 @@ class FHIRFunctions(
message: String,
dequeueCount: Int,
fhirEngine: FHIRReceiverFilter,
- actionHistory: ActionHistory = ActionHistory(TaskAction.route),
+ actionHistory: ActionHistory = ActionHistory(TaskAction.receiver_filter),
) {
val messagesToDispatch = runFhirEngine(message, dequeueCount, fhirEngine, actionHistory)
messagesToDispatch.forEach {
diff --git a/prime-router/src/main/kotlin/fhirengine/engine/FHIRConverter.kt b/prime-router/src/main/kotlin/fhirengine/engine/FHIRConverter.kt
index 9d77b4a0c7d..1216187b2fa 100644
--- a/prime-router/src/main/kotlin/fhirengine/engine/FHIRConverter.kt
+++ b/prime-router/src/main/kotlin/fhirengine/engine/FHIRConverter.kt
@@ -155,12 +155,12 @@ class FHIRConverter(
),
metadata = this.metadata,
topic = queueMessage.topic,
- nextAction = TaskAction.route
+ nextAction = TaskAction.destination_filter
)
// create route event
val routeEvent = ProcessEvent(
- Event.EventAction.ROUTE,
+ Event.EventAction.DESTINATION_FILTER,
report.id,
Options.None,
emptyMap(),
@@ -190,7 +190,7 @@ class FHIRConverter(
routeEvent,
report,
blobInfo.blobUrl,
- FhirRouteQueueMessage(
+ FhirDestinationFilterQueueMessage(
report.id,
blobInfo.blobUrl,
BlobAccess.digestToString(blobInfo.digest),
diff --git a/prime-router/src/main/kotlin/fhirengine/engine/FHIRRouter.kt b/prime-router/src/main/kotlin/fhirengine/engine/FHIRRouter.kt
index db680b628f5..252b782ca99 100644
--- a/prime-router/src/main/kotlin/fhirengine/engine/FHIRRouter.kt
+++ b/prime-router/src/main/kotlin/fhirengine/engine/FHIRRouter.kt
@@ -50,6 +50,7 @@ import org.hl7.fhir.r4.model.Observation
import org.jooq.Field
import java.time.OffsetDateTime
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
/**
* [metadata] mockable metadata
* [settings] mockable settings
diff --git a/prime-router/src/main/kotlin/fhirengine/utils/FHIRBundleHelpers.kt b/prime-router/src/main/kotlin/fhirengine/utils/FHIRBundleHelpers.kt
index 274cdfaba62..7cc42c464dc 100644
--- a/prime-router/src/main/kotlin/fhirengine/utils/FHIRBundleHelpers.kt
+++ b/prime-router/src/main/kotlin/fhirengine/utils/FHIRBundleHelpers.kt
@@ -129,9 +129,7 @@ fun Bundle.getObservations() = this.entry.map { it.resource }.filterIsInstance): List =
if (codes.isEmpty()) {
- // TODO: consider throwing IllegalArgumentException here while implementing
- // https://github.com/CDCgov/prime-reportstream/issues/12705
- emptyList()
+ throw IllegalArgumentException("Invalid mapped condition filter")
} else {
this.getObservations().filter {
it.getMappedConditionCodes().any(codes::contains)
diff --git a/prime-router/src/main/kotlin/report/ReportService.kt b/prime-router/src/main/kotlin/report/ReportService.kt
index b7a8292943a..190d070d31d 100644
--- a/prime-router/src/main/kotlin/report/ReportService.kt
+++ b/prime-router/src/main/kotlin/report/ReportService.kt
@@ -15,12 +15,11 @@ class ReportService(
* Gets the root report up the report_linage table
*
* @param childReportId child report ID
- * @throws IllegalStateException if a root report cannot be found for this report ID
- * @return ReportFile object of the root report
+ * @return ReportFile object of the root report -- of the child report itself if it has no parents
*/
fun getRootReport(childReportId: ReportId): ReportFile {
return reportGraph.getRootReport(childReportId)
- ?: error("No Root Report found for reportId=$childReportId")
+ ?: reportGraph.db.fetchReportFile(childReportId)
}
/**
diff --git a/prime-router/src/main/resources/metadata/fhir_transforms/receivers/enrichments/vt-enrichment.yml b/prime-router/src/main/resources/metadata/fhir_transforms/receivers/enrichments/vt-enrichment.yml
deleted file mode 100644
index 1cd85a84e02..00000000000
--- a/prime-router/src/main/resources/metadata/fhir_transforms/receivers/enrichments/vt-enrichment.yml
+++ /dev/null
@@ -1 +0,0 @@
-extends: classpath:/metadata/fhir_transforms/common/use-pid-14-for-email.yml,classpath:/metadata/fhir_transforms/common/suppress-non-npi.yml
\ No newline at end of file
diff --git a/prime-router/src/main/resources/metadata/fhir_transforms/senders/Flexion/TILabOrder.yml b/prime-router/src/main/resources/metadata/fhir_transforms/senders/Flexion/etor-sender-transform.yml
similarity index 100%
rename from prime-router/src/main/resources/metadata/fhir_transforms/senders/Flexion/TILabOrder.yml
rename to prime-router/src/main/resources/metadata/fhir_transforms/senders/Flexion/etor-sender-transform.yml
diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml
similarity index 100%
rename from prime-router/src/main/resources/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml
rename to prime-router/src/main/resources/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml
diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/IL/IL-receiver-transform.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/IL/IL-receiver-transform.yml
new file mode 100644
index 00000000000..f7d6cc6c446
--- /dev/null
+++ b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/IL/IL-receiver-transform.yml
@@ -0,0 +1,67 @@
+hl7Class: ca.uhn.hl7v2.model.v251.message.ORU_R01
+
+extends: classpath:/metadata/hl7_mapping/ORU_R01/ORU_R01-base.yml
+
+elements:
+
+ - name: il-name-of-the-sending-application
+ condition: 'true'
+ value: [ '"CDC PRIME"' ]
+ hl7Spec: [ 'MSH-4-1' ]
+
+ - name: il-OID-of-the-sending-application
+ condition: 'true'
+ value: [ '"11D2030855"' ]
+ hl7Spec: [ 'MSH-4-2' ]
+
+ - name: il-indicates-that-5.2-is-an-OID
+ condition: 'true'
+ value: [ '"CLIA"' ]
+ hl7Spec: [ 'MSH-4-3' ]
+
+ - name: il-name-of-the-receiving-application
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-5-1' ]
+
+ - name: il-OID-of-the-receiving-application
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-5-2' ]
+
+ - name: il-indicates-that-5.2-is-an-OID
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-5-3' ]
+
+ - name: il-receiving-facility-namespace-ID
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-6-1' ]
+
+ - name: il-receiving-facility-universal-ID
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-6-2' ]
+
+ - name: il-receiving-facility-universal-ID-Type
+ condition: 'true'
+ value: [ '""' ]
+ hl7Spec: [ 'MSH-6-3' ]
+
+ - name: il-patient-race-coding-system
+ resource: 'Bundle.entry.resource.ofType(Patient).extension("http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd").value.coding'
+ condition: '%resource.code.exists()'
+ value: [ '%resource.system.getCodingSystemMapping()' ]
+ hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-10-3' ]
+
+ - name: observation-result-with-aoe
+ # suppressAoe: 'true'
+ resource: '%resource.result.resolve()'
+
+ # suppressNonNPI: true
+ - name: il-suppressNonNPI
+ resource: 'Bundle.entry.resource.ofType(Practitioner)'
+ condition: '(%resource.identifier[0].extension("https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id").value != "NPI") or (%resource.extension("https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority").extension("https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id")[0].value != "NPI") or (%resource.identifier[0].type.coding.code != "NPI") or (%resource.identifier[0].system != "NPI")'
+ value: [ '""' ]
+ hl7Spec: ['%{ORC}-12-1', '%{OBR}-16-1', '%{ORC}-12-9', '%{OBR}-16-9', '%{ORC}-12-13', '%{OBR}-16-13']
\ No newline at end of file
diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MS/MS-receiver-tranform.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MS/MS-receiver-tranform.yml
new file mode 100644
index 00000000000..4b525c8d183
--- /dev/null
+++ b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/MS/MS-receiver-tranform.yml
@@ -0,0 +1,62 @@
+hl7Class: ca.uhn.hl7v2.model.v251.message.ORU_R01
+
+extends: classpath:/metadata/hl7_mapping/ORU_R01/ORU_R01-base.yml
+
+elements:
+
+ - name: xtn-use-code-from-system
+ condition: '%resource.system = "email"'
+ value: [ '"NET"' ]
+ hl7Spec: [ '%{hl7TelecomPath}-2' ]
+
+ - name: ms-name-of-the-sender-application-Namespace-Id
+ condition: 'true'
+ value: [ '"CDC PRIME - Atlanta, Georgia (Dekalb)"' ]
+ hl7Spec: [ 'MSH-3-1' ]
+
+ - name: ms-sender-application-Universal-Id
+ condition: 'true'
+ value: [ '"2.16.840.1.114222.4.1.237821"' ]
+ hl7Spec: [ 'MSH-3-2' ]
+
+ - name: ms-sender-application-Universal-Id-Type
+ condition: 'true'
+ value: [ '"ISO"' ]
+ hl7Spec: [ 'MSH-3-3' ]
+
+ - name: ms-name-of-the-receiving-application
+ condition: 'true'
+ value: [ '"MSDH-ELR"' ]
+ hl7Spec: [ 'MSH-5-1' ]
+
+ - name: ms-OID-of-the-receiving-application
+ condition: 'true'
+ value: [ '"2.16.840.1.114222.4.3.3.8.1.3"' ]
+ hl7Spec: [ 'MSH-5-2' ]
+
+ - name: ms-indicates-that-5.2-is-an-OID
+ condition: 'true'
+ value: [ '"ISO"' ]
+ hl7Spec: [ 'MSH-5-3' ]
+
+ - name: ms-receiving-facility-namespace-ID
+ condition: 'true'
+ value: [ '"MSDOH"' ]
+ hl7Spec: [ 'MSH-6-1' ]
+
+ - name: ms-receiving-facility-universal-ID
+ condition: 'true'
+ value: [ '"2.16.840.1.114222.1.3645"' ]
+ hl7Spec: [ 'MSH-6-2' ]
+
+ - name: ms-receiving-facility-universal-ID-Type
+ condition: 'true'
+ value: [ '"ISO"' ]
+ hl7Spec: [ 'MSH-6-3' ]
+
+ - name: ms-patient-race-coding-system
+ resource: 'Bundle.entry.resource.ofType(Patient).extension("http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd").value.coding'
+ condition: '%resource.code.exists()'
+ value: [ '%resource.system.getCodingSystemMapping()' ]
+ hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-10-3' ]
+
diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/NM/NM-receiver-transform.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/NM/NM-receiver-transform.yml
new file mode 100644
index 00000000000..f2e39331103
--- /dev/null
+++ b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/NM/NM-receiver-transform.yml
@@ -0,0 +1,55 @@
+hl7Class: ca.uhn.hl7v2.model.v251.message.ORU_R01
+
+extends: classpath:/metadata/hl7_mapping/ORU_R01/ORU_R01-base.yml
+
+constants:
+ MSH: '/MSH'
+
+elements:
+ - name: nm-patient-race-coding-system
+ resource: 'Bundle.entry.resource.ofType(Patient).extension("http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd").value.coding'
+ condition: '%resource.code.exists()'
+ value: [ '%resource.system.getCodingSystemMapping()' ]
+ hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-10-3' ]
+
+ - name: nm-sending-application-namespace-id
+ value: [ '"CDC PRIME - Atlanta, Georgia (Dekalb)"' ]
+ hl7Spec: [ '%{MSH}-3-1' ]
+
+ - name: nm-sending-application-universal-id
+ value: [ '"2.16.840.1.114222.4.1.237821"' ]
+ hl7Spec: [ '%{MSH}-3-2' ]
+
+ - name: nm-sending-application-universal-id-type
+ value: [ '"ISO"' ]
+ hl7Spec: [ '%{MSH}-3-3' ]
+
+ - name: nm-receiving-application-namespace-id
+ value: [ '"NMDOH"' ]
+ hl7Spec: [ '%{MSH}-5-1' ]
+
+ - name: nm-receiving-application-universal-id
+ value: [ '"2.16.840.1.113883.3.5364"' ]
+ hl7Spec: [ '%{MSH}-5-2' ]
+
+ - name: nm-receiving-application-universal-id-type
+ value: [ '"ISO"' ]
+ hl7Spec: [ '%{MSH}-5-3' ]
+
+ - name: nm-receiving-facility-namespace-id
+ value: [ '"NMDOH"' ]
+ hl7Spec: [ '%{MSH}-6-1' ]
+
+ - name: nm-receiving-facility-universal-id
+ value: [ '"2.16.840.1.113883.3.5364"' ]
+ hl7Spec: [ '%{MSH}-6-2' ]
+
+ - name: nm-receiving-facility-universal-id-type
+ value: [ '"ISO"' ]
+ hl7Spec: [ '%{MSH}-6-3' ]
+
+ - name: nm-race-unknown-as-blank
+ resource: 'Bundle.entry.resource.ofType(Patient).extension("http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd")'
+ condition: '%resource.value[x].coding[0].code = "UNK" or %resource.value.coding[0].code = "ASKU"'
+ value: [ '""' ]
+ hl7Spec: [ '%{PID}-10-1' , '%{PID}-10-2' , '%{PID}-10-3' , '%{PID}-10-7' , '%{PID}-10-9' ]
diff --git a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml
index f7dad6dae6b..60eeea20001 100644
--- a/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml
+++ b/prime-router/src/main/resources/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml
@@ -8,3 +8,33 @@ elements:
condition: '%resource.code.exists()'
value: [ '%resource.system.getCodingSystemMapping()' ]
hl7Spec: [ '/PATIENT_RESULT/PATIENT/PID-10-3' ]
+
+ - name: OK-name-of-the-receiving-application
+ condition: 'true'
+ value: [ '"OSDHMessaging"' ]
+ hl7Spec: [ 'MSH-5-1' ]
+
+ - name: OK-OID-of-the-receiving-application
+ condition: 'true'
+ value: [ '"2.16.840.1.113883.3.1014.4"' ]
+ hl7Spec: [ 'MSH-5-2' ]
+
+ - name: OK-indicates-that-5.2-is-an-OID
+ condition: 'true'
+ value: [ '"ISO"' ]
+ hl7Spec: [ 'MSH-5-3' ]
+
+ - name: OK-receiving-facility-namespace-ID
+ condition: 'true'
+ value: [ '"OSDH"' ]
+ hl7Spec: [ 'MSH-6-1' ]
+
+ - name: OK-receiving-facility-universal-ID
+ condition: 'true'
+ value: [ '"2.16.840.1.113883.3.1014"' ]
+ hl7Spec: [ 'MSH-6-2' ]
+
+ - name: OK-receiving-facility-universal-ID-Type
+ condition: 'true'
+ value: [ '"ISO"' ]
+ hl7Spec: [ 'MSH-6-3' ]
\ No newline at end of file
diff --git a/prime-router/src/test/kotlin/common/UniversalPipelineTestUtils.kt b/prime-router/src/test/kotlin/common/UniversalPipelineTestUtils.kt
index 0fce1393dde..6859a9e492b 100644
--- a/prime-router/src/test/kotlin/common/UniversalPipelineTestUtils.kt
+++ b/prime-router/src/test/kotlin/common/UniversalPipelineTestUtils.kt
@@ -3,26 +3,43 @@ package gov.cdc.prime.router.common
import assertk.assertThat
import assertk.assertions.hasSize
import assertk.assertions.isEqualTo
+import gov.cdc.prime.router.ClientSource
import gov.cdc.prime.router.CustomerStatus
import gov.cdc.prime.router.DeepOrganization
+import gov.cdc.prime.router.FileSettings
import gov.cdc.prime.router.MimeFormat
+import gov.cdc.prime.router.Options
import gov.cdc.prime.router.Organization
import gov.cdc.prime.router.Receiver
import gov.cdc.prime.router.Report
+import gov.cdc.prime.router.ReportStreamConditionFilter
import gov.cdc.prime.router.Topic
import gov.cdc.prime.router.UniversalPipelineSender
+import gov.cdc.prime.router.azure.BlobAccess
import gov.cdc.prime.router.azure.DataAccessTransaction
+import gov.cdc.prime.router.azure.Event
+import gov.cdc.prime.router.azure.ProcessEvent
+import gov.cdc.prime.router.azure.WorkflowEngine
+import gov.cdc.prime.router.azure.db.Tables
+import gov.cdc.prime.router.azure.db.enums.TaskAction
import gov.cdc.prime.router.azure.db.tables.ItemLineage
import gov.cdc.prime.router.azure.db.tables.ReportLineage
+import gov.cdc.prime.router.azure.db.tables.pojos.Action
import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile
import gov.cdc.prime.router.db.ReportStreamTestDatabaseContainer
+import gov.cdc.prime.router.fhirengine.azure.FHIRFunctions
import gov.cdc.prime.router.history.db.ReportGraph
+import gov.cdc.prime.router.metadata.LookupTable
import gov.cdc.prime.router.report.ReportService
+import gov.cdc.prime.router.unittest.UnitTestUtils
import org.jooq.impl.DSL
+import org.testcontainers.containers.GenericContainer
+import java.time.OffsetDateTime
@Suppress("ktlint:standard:max-line-length")
const val validFHIRRecord1 =
"""{"resourceType":"Bundle","id":"1667861767830636000.7db38d22-b713-49fc-abfa-2edba9c12347","meta":{"lastUpdated":"2022-11-07T22:56:07.832+00:00"},"identifier":{"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},"type":"message","timestamp":"2021-08-03T13:15:11.015+00:00","entry":[{"fullUrl":"Observation/d683b42a-bf50-45e8-9fce-6c0531994f09","resource":{"resourceType":"Observation","id":"d683b42a-bf50-45e8-9fce-6c0531994f09","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"80382-5"}],"text":"Flu A"},"subject":{"reference":"Patient/9473889b-b2b9-45ac-a8d8-191f27132912"},"performer":[{"reference":"Organization/1a0139b9-fc23-450b-9b6c-cd081e5cea9d"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260373001","display":"Detected"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0078","code":"A","display":"Abnormal"}]}],"method":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/testkit-name-id","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/equipment-uid","valueCoding":{"code":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B_Becton, Dickinson and Company (BD)"}}],"coding":[{"display":"BD Veritor System for Rapid Detection of SARS-CoV-2 & Flu A+B*"}]},"specimen":{"reference":"Specimen/52a582e4-d389-42d0-b738-bee51cf5244d"},"device":{"reference":"Device/78dc4d98-2958-43a3-a445-76ceef8c0698"}}}]}"""
+const val validFHIRRecord1Identifier = "1234d1d1-95fe-462c-8ac6-46728dba581c"
@Suppress("ktlint:standard:max-line-length")
const val conditionCodedValidFHIRRecord1 =
@@ -211,6 +228,66 @@ object UniversalPipelineTestUtils {
),
)
+ /**
+ * fetch child reports associated with a [parent] report and ensure we find an [expected] number of children
+ */
+ fun fetchChildReports(
+ parent: Report,
+ txn: DataAccessTransaction,
+ expected: Int? = null,
+ ): List {
+ val itemLineages = DSL
+ .using(txn)
+ .select(ItemLineage.ITEM_LINEAGE.asterisk())
+ .from(ItemLineage.ITEM_LINEAGE)
+ .where(ItemLineage.ITEM_LINEAGE.PARENT_REPORT_ID.eq(parent.id))
+ .fetchInto(gov.cdc.prime.router.azure.db.tables.pojos.ItemLineage::class.java)
+
+ if (expected != null) {
+ assertThat(itemLineages).hasSize(expected)
+ assertThat(itemLineages.map { it.childIndex }).isEqualTo(MutableList(expected) { 1 })
+
+ if (parent.itemCount > 1) {
+ assertThat(itemLineages.map { it.parentIndex }).isEqualTo((1..expected).toList())
+ } else {
+ assertThat(itemLineages.map { it.parentIndex }).isEqualTo(MutableList(expected) { 1 })
+ }
+ }
+
+ val reportLineages = DSL
+ .using(txn)
+ .select(ReportLineage.REPORT_LINEAGE.asterisk())
+ .from(ReportLineage.REPORT_LINEAGE)
+ .where(ReportLineage.REPORT_LINEAGE.PARENT_REPORT_ID.eq(parent.id))
+ .fetchInto(gov.cdc.prime.router.azure.db.tables.pojos.ReportLineage::class.java)
+
+ if (expected != null) {
+ assertThat(reportLineages).hasSize(expected)
+ }
+
+ val childReportIds = reportLineages.map {
+ it.childReportId
+ }
+ val reportFiles = DSL
+ .using(txn)
+ .select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
+ .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .where(
+ gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID.`in`(
+ childReportIds
+ )
+ )
+ .fetchInto(ReportFile::class.java)
+ if (expected != null) {
+ assertThat(reportFiles).hasSize(expected)
+ }
+ assertThat(itemLineages).transform { lineages -> lineages.map { it.childReportId }.sorted() }
+ .isEqualTo(reportFiles.map { it.reportId }.sorted())
+
+ return reportFiles
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
fun verifyLineageAndFetchCreatedReportFiles(
previousStepReport: Report,
expectedRootReport: Report,
@@ -270,4 +347,201 @@ object UniversalPipelineTestUtils {
return reportFiles
}
+
+ data class ReceiverSetupData(
+ val name: String,
+ val orgName: String = "phd",
+ val topic: Topic = Topic.FULL_ELR,
+ val jurisdictionalFilter: List = emptyList(),
+ val qualityFilter: List = emptyList(),
+ val routingFilter: List = emptyList(),
+ val processingModeFilter: List = emptyList(),
+ val conditionFilter: List = emptyList(),
+ val mappedConditionFilter: ReportStreamConditionFilter = emptyList(),
+ val status: CustomerStatus = CustomerStatus.ACTIVE,
+ )
+
+ fun createReceivers(receiverSetupDataList: List): List {
+ return receiverSetupDataList.map {
+ Receiver(
+ it.name,
+ it.orgName,
+ it.topic,
+ it.status,
+ "classpath:/metadata/hl7_mapping/ORU_R01/ORU_R01-base.yml",
+ timing = Receiver.Timing(numberPerDay = 1, maxReportCount = 1, whenEmpty = Receiver.WhenEmpty()),
+ jurisdictionalFilter = it.jurisdictionalFilter,
+ qualityFilter = it.qualityFilter,
+ routingFilter = it.routingFilter,
+ processingModeFilter = it.processingModeFilter,
+ conditionFilter = it.conditionFilter,
+ mappedConditionFilter = it.mappedConditionFilter
+ )
+ }
+ }
+
+ fun createOrganizationWithReceivers(receiverList: List): DeepOrganization {
+ return DeepOrganization(
+ "phd",
+ "test",
+ Organization.Jurisdiction.FEDERAL,
+ senders = listOf(
+ hl7Sender,
+ fhirSender,
+ hl7SenderWithNoTransform,
+ fhirSenderWithNoTransform,
+ senderWithValidation
+ ),
+ receivers = receiverList
+ )
+ }
+
+ fun createFHIRFunctionsInstance(): FHIRFunctions {
+ val settings = FileSettings().loadOrganizations(universalPipelineOrganization)
+ val metadata = UnitTestUtils.simpleMetadata
+ metadata.lookupTableStore += mapOf(
+ "observation-mapping" to LookupTable("observation-mapping", emptyList())
+ )
+ val workflowEngine = WorkflowEngine.Builder()
+ .metadata(metadata)
+ .settingsProvider(settings)
+ .databaseAccess(ReportStreamTestDatabaseContainer.testDatabaseAccess)
+ .build()
+ return FHIRFunctions(workflowEngine, databaseAccess = ReportStreamTestDatabaseContainer.testDatabaseAccess)
+ }
+
+ fun getBlobContainerMetadata(azuriteContainer: GenericContainer<*>): BlobAccess.BlobContainerMetadata {
+ val blobConnectionString =
+ """DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=keydevstoreaccount1;
+ BlobEndpoint=http://${azuriteContainer.host}:${
+
+ azuriteContainer.getMappedPort(
+ 10000
+ )
+ }/devstoreaccount1;QueueEndpoint=http://${azuriteContainer.host}:${
+ azuriteContainer.getMappedPort(
+ 10001
+ )
+ }/devstoreaccount1;"""
+ return BlobAccess.BlobContainerMetadata(
+ "container1",
+ blobConnectionString
+ )
+ }
+
+ fun createReport(
+ reportContents: String,
+ action: TaskAction,
+ event: Event.EventAction,
+ azuriteContainer: GenericContainer<*>,
+ previousAction: TaskAction = TaskAction.receive,
+ parentReport: Report? = null,
+ ): Report {
+ val blobUrl = BlobAccess.uploadBlob(
+ "${TaskAction.receive.literal}/mr_fhir_face.fhir",
+ reportContents.toByteArray(),
+ getBlobContainerMetadata(azuriteContainer)
+ )
+
+ return createReport(
+ MimeFormat.FHIR,
+ previousAction,
+ action,
+ event,
+ Topic.FULL_ELR,
+ parentReport,
+ blobUrl
+ )
+ }
+
+ fun createReport(
+ fileFormat: MimeFormat,
+ currentAction: TaskAction,
+ nextAction: TaskAction,
+ nextEventAction: Event.EventAction,
+ topic: Topic,
+ parentReport: Report? = null,
+ bodyURL: String? = null,
+ ): Report {
+ val report = Report(
+ fileFormat,
+ listOf(
+ ClientSource(
+ organization = universalPipelineOrganization.name,
+ client = "Test Sender"
+ )
+ ),
+ 1,
+ metadata = UnitTestUtils.simpleMetadata,
+ nextAction = nextAction,
+ topic = topic
+ )
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val action = Action().setActionName(currentAction)
+ val actionId = ReportStreamTestDatabaseContainer.testDatabaseAccess.insertAction(txn, action)
+ report.bodyURL = bodyURL ?: "http://${report.id}.${fileFormat.toString().lowercase()}"
+
+ val reportFile = ReportFile().setSchemaTopic(topic)
+ .setReportId(report.id)
+ .setActionId(actionId)
+ .setSchemaName("")
+ .setBodyFormat(fileFormat.toString())
+ .setItemCount(1)
+ .setExternalName("test-external-name")
+ .setBodyUrl(report.bodyURL)
+ .setSendingOrg(universalPipelineOrganization.name)
+ .setSendingOrgClient("Test Sender")
+
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.insertReportFile(
+ reportFile, txn, action
+ )
+ if (parentReport != null) {
+ ReportStreamTestDatabaseContainer.testDatabaseAccess
+ .insertReportLineage(
+ gov.cdc.prime.router.azure.db.tables.pojos.ReportLineage(
+ null,
+ actionId,
+ parentReport.id,
+ report.id,
+ OffsetDateTime.now()
+ ),
+ txn
+ )
+ }
+
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.insertTask(
+ report,
+ fileFormat.toString().lowercase(),
+ report.bodyURL,
+ nextAction = ProcessEvent(
+ nextEventAction,
+ report.id,
+ Options.None,
+ emptyMap(),
+ emptyList()
+ ),
+ txn
+ )
+ }
+
+ return report
+ }
+
+ /**
+ * check the action table for a list of [expectedTaskActions]
+ */
+ fun checkActionTable(expectedTaskActions: List) {
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val actionRecords = DSL.using(txn)
+ .select(Tables.ACTION.asterisk())
+ .from(Tables.ACTION)
+ .fetchInto(
+ Action::class.java
+ )
+
+ for (i in 0 until actionRecords.size) {
+ assertThat(expectedTaskActions[i]).isEqualTo(actionRecords[i].actionName)
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt
index 2286c09a0f0..a46ef216ca4 100644
--- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRConverterIntegrationTests.kt
@@ -26,12 +26,12 @@ import gov.cdc.prime.router.azure.db.tables.pojos.ReportFile
import gov.cdc.prime.router.cli.ObservationMappingConstants
import gov.cdc.prime.router.cli.tests.CompareData
import gov.cdc.prime.router.common.TestcontainersUtils
+import gov.cdc.prime.router.common.UniversalPipelineTestUtils.fetchChildReports
import gov.cdc.prime.router.common.UniversalPipelineTestUtils.fhirSenderWithNoTransform
import gov.cdc.prime.router.common.UniversalPipelineTestUtils.hl7Sender
import gov.cdc.prime.router.common.UniversalPipelineTestUtils.hl7SenderWithNoTransform
import gov.cdc.prime.router.common.UniversalPipelineTestUtils.senderWithValidation
import gov.cdc.prime.router.common.UniversalPipelineTestUtils.universalPipelineOrganization
-import gov.cdc.prime.router.common.UniversalPipelineTestUtils.verifyLineageAndFetchCreatedReportFiles
import gov.cdc.prime.router.common.badEncodingHL7Record
import gov.cdc.prime.router.common.cleanHL7Record
import gov.cdc.prime.router.common.cleanHL7RecordConverted
@@ -51,7 +51,8 @@ import gov.cdc.prime.router.common.validRadxMarsHL7MessageConverted
import gov.cdc.prime.router.db.ReportStreamTestDatabaseContainer
import gov.cdc.prime.router.db.ReportStreamTestDatabaseSetupExtension
import gov.cdc.prime.router.fhirengine.engine.FHIRConverter
-import gov.cdc.prime.router.fhirengine.engine.FhirRouteQueueMessage
+import gov.cdc.prime.router.fhirengine.engine.FhirDestinationFilterQueueMessage
+import gov.cdc.prime.router.fhirengine.engine.elrDestinationFilterQueueName
import gov.cdc.prime.router.history.DetailedActionLog
import gov.cdc.prime.router.metadata.LookupTable
import gov.cdc.prime.router.unittest.UnitTestUtils
@@ -225,7 +226,7 @@ class FHIRConverterIntegrationTests {
fhirFunctions.doConvert(queueMessage, 1, createFHIRConverter())
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routedReports = verifyLineageAndFetchCreatedReportFiles(receiveReport, receiveReport, txn, 2)
+ val routedReports = fetchChildReports(receiveReport, txn, 2)
// Verify that the expected FHIR bundles were uploaded
val reportAndBundles =
routedReports.map {
@@ -255,8 +256,8 @@ class FHIRConverterIntegrationTests {
}
}
- val expectedRouteQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
- FhirRouteQueueMessage(
+ val expectedQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
+ FhirDestinationFilterQueueMessage(
report.reportId,
report.bodyUrl,
BlobAccess.digestToString(BlobAccess.sha256Digest(fhirBundle)),
@@ -266,7 +267,10 @@ class FHIRConverterIntegrationTests {
}.map { it.serialize() }
verify(exactly = 2) {
- QueueAccess.sendMessage("elr-fhir-route", match { expectedRouteQueueMessages.contains(it) })
+ QueueAccess.sendMessage(
+ elrDestinationFilterQueueName,
+ match { expectedQueueMessages.contains(it) }
+ )
}
val actionLogs = DSL.using(txn).select(Tables.ACTION_LOG.asterisk()).from(Tables.ACTION_LOG)
@@ -342,7 +346,7 @@ class FHIRConverterIntegrationTests {
fhirFunctions.doConvert(queueMessage, 1, createFHIRConverter())
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routedReports = verifyLineageAndFetchCreatedReportFiles(receiveReport, receiveReport, txn, 2)
+ val routedReports = fetchChildReports(receiveReport, txn, 2)
// Verify that the expected FHIR bundles were uploaded
val reportAndBundles =
routedReports.map {
@@ -354,8 +358,8 @@ class FHIRConverterIntegrationTests {
.map { Pair(it.first, it.second.toString(Charset.defaultCharset())) }
assertThat(reportAndBundles).transform { pairs -> pairs.map { it.second } }
.containsOnly(conditionCodedValidFHIRRecord1, validFHIRRecord2)
- val expectedRouteQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
- FhirRouteQueueMessage(
+ val expectedQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
+ FhirDestinationFilterQueueMessage(
report.reportId,
report.bodyUrl,
BlobAccess.digestToString(BlobAccess.sha256Digest(fhirBundle.toByteArray())),
@@ -364,7 +368,10 @@ class FHIRConverterIntegrationTests {
)
}.map { it.serialize() }
verify(exactly = 2) {
- QueueAccess.sendMessage("elr-fhir-route", match { expectedRouteQueueMessages.contains(it) })
+ QueueAccess.sendMessage(
+ elrDestinationFilterQueueName,
+ match { expectedQueueMessages.contains(it) }
+ )
}
val actionLogs = DSL.using(txn).select(Tables.ACTION_LOG.asterisk()).from(Tables.ACTION_LOG)
@@ -418,7 +425,7 @@ class FHIRConverterIntegrationTests {
fhirFunctions.doConvert(queueMessage, 1, createFHIRConverter())
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routedReports = verifyLineageAndFetchCreatedReportFiles(receiveReport, receiveReport, txn, 1)
+ val routedReports = fetchChildReports(receiveReport, txn, 1)
// Verify that the expected FHIR bundles were uploaded
val reportAndBundles =
routedReports.map {
@@ -439,8 +446,8 @@ class FHIRConverterIntegrationTests {
}
}
- val expectedRouteQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
- FhirRouteQueueMessage(
+ val expectedQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
+ FhirDestinationFilterQueueMessage(
report.reportId,
report.bodyUrl,
BlobAccess.digestToString(BlobAccess.sha256Digest(fhirBundle)),
@@ -449,7 +456,10 @@ class FHIRConverterIntegrationTests {
)
}.map { it.serialize() }
verify(exactly = 1) {
- QueueAccess.sendMessage("elr-fhir-route", match { expectedRouteQueueMessages.contains(it) })
+ QueueAccess.sendMessage(
+ elrDestinationFilterQueueName,
+ match { expectedQueueMessages.contains(it) }
+ )
}
val actionLogs = DSL.using(txn).select(Tables.ACTION_LOG.asterisk()).from(Tables.ACTION_LOG)
@@ -487,7 +497,7 @@ class FHIRConverterIntegrationTests {
fhirFunctions.doConvert(queueMessage, 1, createFHIRConverter())
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routedReports = verifyLineageAndFetchCreatedReportFiles(receiveReport, receiveReport, txn, 2)
+ val routedReports = fetchChildReports(receiveReport, txn, 2)
// Verify that the expected FHIR bundles were uploaded
val reportAndBundles =
routedReports.map {
@@ -515,8 +525,8 @@ class FHIRConverterIntegrationTests {
}
}
- val expectedRouteQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
- FhirRouteQueueMessage(
+ val expectedQueueMessages = reportAndBundles.map { (report, fhirBundle) ->
+ FhirDestinationFilterQueueMessage(
report.reportId,
report.bodyUrl,
BlobAccess.digestToString(BlobAccess.sha256Digest(fhirBundle)),
@@ -526,7 +536,10 @@ class FHIRConverterIntegrationTests {
}.map { it.serialize() }
verify(exactly = 2) {
- QueueAccess.sendMessage("elr-fhir-route", match { expectedRouteQueueMessages.contains(it) })
+ QueueAccess.sendMessage(
+ elrDestinationFilterQueueName,
+ match { expectedQueueMessages.contains(it) }
+ )
}
val actionLogs = DSL.using(txn).select(Tables.ACTION_LOG.asterisk()).from(Tables.ACTION_LOG)
@@ -559,7 +572,7 @@ class FHIRConverterIntegrationTests {
QueueAccess.sendMessage(any(), any())
}
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- verifyLineageAndFetchCreatedReportFiles(receiveReport, receiveReport, txn, 1)
+ fetchChildReports(receiveReport, txn, 1)
}
}
}
\ No newline at end of file
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt
new file mode 100644
index 00000000000..64b8472beae
--- /dev/null
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRDestinationFilterIntegrationTests.kt
@@ -0,0 +1,406 @@
+package gov.cdc.prime.router.fhirengine.azure
+
+import assertk.assertThat
+import assertk.assertions.containsOnly
+import assertk.assertions.hasSize
+import assertk.assertions.isEmpty
+import assertk.assertions.isEqualTo
+import assertk.assertions.isEqualToIgnoringGivenProperties
+import assertk.assertions.isInstanceOf
+import gov.cdc.prime.router.ActionLog
+import gov.cdc.prime.router.CustomerStatus
+import gov.cdc.prime.router.DeepOrganization
+import gov.cdc.prime.router.FileSettings
+import gov.cdc.prime.router.Report
+import gov.cdc.prime.router.ReportStreamFilter
+import gov.cdc.prime.router.Sender
+import gov.cdc.prime.router.Topic
+import gov.cdc.prime.router.azure.BlobAccess
+import gov.cdc.prime.router.azure.DatabaseLookupTableAccess
+import gov.cdc.prime.router.azure.Event
+import gov.cdc.prime.router.azure.QueueAccess
+import gov.cdc.prime.router.azure.db.Tables
+import gov.cdc.prime.router.azure.db.enums.TaskAction
+import gov.cdc.prime.router.azure.observability.event.AzureEventService
+import gov.cdc.prime.router.azure.observability.event.AzureEventUtils
+import gov.cdc.prime.router.azure.observability.event.LocalAzureEventServiceImpl
+import gov.cdc.prime.router.azure.observability.event.ReportAcceptedEvent
+import gov.cdc.prime.router.azure.observability.event.ReportNotRoutedEvent
+import gov.cdc.prime.router.common.TestcontainersUtils
+import gov.cdc.prime.router.common.UniversalPipelineTestUtils
+import gov.cdc.prime.router.common.validFHIRRecord1
+import gov.cdc.prime.router.db.ReportStreamTestDatabaseContainer
+import gov.cdc.prime.router.db.ReportStreamTestDatabaseSetupExtension
+import gov.cdc.prime.router.fhirengine.engine.FHIRDestinationFilter
+import gov.cdc.prime.router.fhirengine.engine.FhirReceiverFilterQueueMessage
+import gov.cdc.prime.router.fhirengine.engine.elrReceiverFilterQueueName
+import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder
+import gov.cdc.prime.router.history.db.ReportGraph
+import gov.cdc.prime.router.metadata.LookupTable
+import gov.cdc.prime.router.report.ReportService
+import gov.cdc.prime.router.unittest.UnitTestUtils
+import io.mockk.every
+import io.mockk.mockkConstructor
+import io.mockk.mockkObject
+import io.mockk.unmockkAll
+import io.mockk.verify
+import org.apache.logging.log4j.kotlin.Logging
+import org.jooq.impl.DSL
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.testcontainers.junit.jupiter.Container
+import org.testcontainers.junit.jupiter.Testcontainers
+import java.io.File
+import java.util.UUID
+
+private const val VALID_FHIR_URL = "src/test/resources/fhirengine/engine/valid_data.fhir"
+
+@Testcontainers
+@ExtendWith(ReportStreamTestDatabaseSetupExtension::class)
+class FHIRDestinationFilterIntegrationTests : Logging {
+ // patient must reside in Colorado
+ val jurisdictionalFilterCo: ReportStreamFilter = listOf("Bundle.entry.resource.ofType(Patient).address.state='CO'")
+
+ // patient must reside in Illinois
+ val jurisdictionalFilterIl: ReportStreamFilter = listOf("Bundle.entry.resource.ofType(Patient).address.state='IL'")
+
+ @Container
+ val azuriteContainer = TestcontainersUtils.createAzuriteContainer(
+ customImageName = "azurite_fhirfunctionintegration1",
+ customEnv = mapOf(
+ "AZURITE_ACCOUNTS" to "devstoreaccount1:keydevstoreaccount1"
+ )
+ )
+
+ val azureEventsService = LocalAzureEventServiceImpl()
+
+ @BeforeEach
+ fun beforeEach() {
+ mockkObject(QueueAccess)
+ mockkObject(BlobAccess)
+ mockkObject(BlobAccess.BlobContainerMetadata)
+
+ every { QueueAccess.sendMessage(any(), any()) } returns Unit
+ every { BlobAccess getProperty "defaultBlobMetadata" } returns UniversalPipelineTestUtils
+ .getBlobContainerMetadata(azuriteContainer)
+ every { BlobAccess.BlobContainerMetadata.build(any(), any()) } returns
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ mockkConstructor(DatabaseLookupTableAccess::class)
+ }
+
+ @AfterEach
+ fun afterEach() {
+ unmockkAll()
+ azureEventsService.events.clear()
+ }
+
+ fun createDestinationFilter(
+ azureEventService: AzureEventService,
+ org: DeepOrganization? = null,
+ ): FHIRDestinationFilter {
+ val settings = FileSettings().loadOrganizations(org ?: UniversalPipelineTestUtils.universalPipelineOrganization)
+ val metadata = UnitTestUtils.simpleMetadata
+ metadata.lookupTableStore += mapOf(
+ "observation-mapping" to LookupTable("observation-mapping", emptyList())
+ )
+ return FHIRDestinationFilter(
+ metadata,
+ settings,
+ reportService = ReportService(ReportGraph(ReportStreamTestDatabaseContainer.testDatabaseAccess)),
+ azureEventService = azureEventService
+ )
+ }
+
+ fun generateQueueMessage(action: TaskAction, report: Report, blobContents: String, sender: Sender): String {
+ return """
+ {
+ "type": "${action.literal}",
+ "reportId": "${report.id}",
+ "blobURL": "${report.bodyURL}",
+ "digest": "${BlobAccess.digestToString(BlobAccess.sha256Digest(blobContents.toByteArray()))}",
+ "blobSubFolderName": "${sender.fullName}",
+ "topic": "${sender.topic.jsonVal}",
+ "schemaName": "${sender.schemaName}"
+ }
+ """.trimIndent()
+ }
+
+ @Test
+ fun `should send valid FHIR report only to receivers listening to full-elr`() {
+ // set up
+ val reportContents = validFHIRRecord1
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.destination_filter,
+ Event.EventAction.DESTINATION_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ TaskAction.destination_filter,
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+ val receiverList = UniversalPipelineTestUtils.createReceivers(
+ listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true")
+ ),
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "y",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true")
+ ),
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "z",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ topic = Topic.TEST
+ ),
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "inactive-receiver",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ topic = Topic.TEST,
+ status = CustomerStatus.INACTIVE
+ )
+ )
+ )
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receiverList)
+ val destinationFilter = createDestinationFilter(azureEventsService, org)
+
+ // execute
+ fhirFunctions.doDestinationFilter(queueMessage, 1, destinationFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReports = UniversalPipelineTestUtils.fetchChildReports(report, txn, 2)
+ val routedBundles = routedReports.map {
+ String(
+ BlobAccess.downloadBlobAsByteArray(
+ it.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ )
+ }
+ assertThat(routedBundles).containsOnly(validFHIRRecord1)
+
+ // check queue message
+ val expectedRouteQueueMessages = routedReports.flatMap { report ->
+ listOf(
+ FhirReceiverFilterQueueMessage(
+ report.reportId,
+ report.bodyUrl,
+ BlobAccess.digestToString(report.blobDigest),
+ "phd.fhir-elr-no-transform",
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform.topic,
+ "phd.x"
+ ),
+ FhirReceiverFilterQueueMessage(
+ report.reportId,
+ report.bodyUrl,
+ BlobAccess.digestToString(report.blobDigest),
+ "phd.fhir-elr-no-transform",
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform.topic,
+ "phd.y"
+ )
+ )
+ }.map {
+ it.serialize()
+ }
+
+ verify(exactly = 2) {
+ QueueAccess.sendMessage(
+ elrReceiverFilterQueueName,
+ match {
+ expectedRouteQueueMessages.contains(it)
+ }
+ )
+ }
+
+ // check events
+ assertThat(azureEventsService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventsService.events.single()).isEqualTo(
+ ReportAcceptedEvent(
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ AzureEventUtils.getObservationSummaries(bundle),
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.destination_filter))
+ }
+ }
+
+ @Test
+ fun `should respect jurisdictional filter and send message`() {
+ // set up
+ val reportContents = File(VALID_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.destination_filter,
+ Event.EventAction.DESTINATION_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ TaskAction.destination_filter,
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+ val receivers = UniversalPipelineTestUtils.createReceivers(
+ listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = jurisdictionalFilterCo
+ )
+ )
+ )
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val destinationFilter = createDestinationFilter(azureEventsService, org)
+
+ // execute
+ fhirFunctions.doDestinationFilter(queueMessage, 1, destinationFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ val routedBundle = BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ assertThat(reportContents).isEqualTo(String(routedBundle))
+
+ // check queue message
+ val expectedQueueMessage = FhirReceiverFilterQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform.topic,
+ "phd.x"
+ )
+
+ // filter should permit message and should not mangle message
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(
+ elrReceiverFilterQueueName,
+ expectedQueueMessage.serialize()
+ )
+ }
+
+ // check events
+ assertThat(azureEventsService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventsService.events.single()).isEqualTo(
+ ReportAcceptedEvent(
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ AzureEventUtils.getObservationSummaries(bundle),
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.destination_filter))
+ }
+ }
+
+ @Test
+ fun `should respect jurisdictional filter and not send message`() {
+ // set up
+ val reportContents = File(VALID_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.destination_filter,
+ Event.EventAction.DESTINATION_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ TaskAction.destination_filter,
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = jurisdictionalFilterIl
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val destinationFilter = createDestinationFilter(azureEventsService, org)
+
+ // execute
+ fhirFunctions.doDestinationFilter(queueMessage, 1, destinationFilter)
+
+ // no messages should have been routed due to filter
+ verify(exactly = 0) {
+ QueueAccess.sendMessage(elrReceiverFilterQueueName, any())
+ }
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.destination_filter))
+
+ // we don't log applications of jurisdictional filter to ACTION_LOG at this time
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val actionLogRecords = DSL.using(txn)
+ .select(Tables.ACTION_LOG.asterisk())
+ .from(Tables.ACTION_LOG)
+ .fetchInto(ActionLog::class.java)
+ assertThat(actionLogRecords).isEmpty()
+ }
+
+ // check events
+ assertThat(azureEventsService.events).hasSize(2)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventsService.events.first())
+ .isInstanceOf()
+ .isEqualTo(
+ ReportAcceptedEvent(
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ AzureEventUtils.getObservationSummaries(bundle),
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ )
+ )
+ assertThat(azureEventsService.events.last())
+ .isInstanceOf()
+ .isEqualToIgnoringGivenProperties(
+ ReportNotRoutedEvent(
+ UUID.randomUUID(), // ignored
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ ),
+ ReportNotRoutedEvent::reportId
+ )
+ }
+}
\ No newline at end of file
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt
new file mode 100644
index 00000000000..9698e582162
--- /dev/null
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRReceiverFilterIntegrationTests.kt
@@ -0,0 +1,1022 @@
+package gov.cdc.prime.router.fhirengine.azure
+
+import assertk.assertThat
+import assertk.assertions.hasSameSizeAs
+import assertk.assertions.hasSize
+import assertk.assertions.isEqualTo
+import assertk.assertions.isEqualToIgnoringGivenProperties
+import assertk.assertions.isInstanceOf
+import assertk.assertions.isNull
+import assertk.assertions.isZero
+import assertk.assertions.matchesPredicate
+import gov.cdc.prime.router.ActionLog
+import gov.cdc.prime.router.ActionLogLevel
+import gov.cdc.prime.router.ActionLogScope
+import gov.cdc.prime.router.CodeStringConditionFilter
+import gov.cdc.prime.router.DeepOrganization
+import gov.cdc.prime.router.FileSettings
+import gov.cdc.prime.router.Report
+import gov.cdc.prime.router.ReportStreamFilter
+import gov.cdc.prime.router.ReportStreamFilterType
+import gov.cdc.prime.router.Sender
+import gov.cdc.prime.router.Topic
+import gov.cdc.prime.router.azure.BlobAccess
+import gov.cdc.prime.router.azure.DatabaseLookupTableAccess
+import gov.cdc.prime.router.azure.Event
+import gov.cdc.prime.router.azure.QueueAccess
+import gov.cdc.prime.router.azure.db.Tables
+import gov.cdc.prime.router.azure.db.enums.TaskAction
+import gov.cdc.prime.router.azure.observability.event.AzureEventService
+import gov.cdc.prime.router.azure.observability.event.AzureEventUtils
+import gov.cdc.prime.router.azure.observability.event.LocalAzureEventServiceImpl
+import gov.cdc.prime.router.azure.observability.event.ReceiverFilterFailedEvent
+import gov.cdc.prime.router.azure.observability.event.ReportRouteEvent
+import gov.cdc.prime.router.cli.ObservationMappingConstants
+import gov.cdc.prime.router.common.TestcontainersUtils
+import gov.cdc.prime.router.common.UniversalPipelineTestUtils
+import gov.cdc.prime.router.common.validFHIRRecord1
+import gov.cdc.prime.router.common.validFHIRRecord1Identifier
+import gov.cdc.prime.router.db.ReportStreamTestDatabaseContainer
+import gov.cdc.prime.router.db.ReportStreamTestDatabaseSetupExtension
+import gov.cdc.prime.router.fhirengine.engine.FHIRReceiverFilter
+import gov.cdc.prime.router.fhirengine.engine.FhirTranslateQueueMessage
+import gov.cdc.prime.router.fhirengine.engine.elrTranslationQueueName
+import gov.cdc.prime.router.fhirengine.utils.FhirTranscoder
+import gov.cdc.prime.router.fhirengine.utils.addMappedConditions
+import gov.cdc.prime.router.fhirengine.utils.deleteResource
+import gov.cdc.prime.router.fhirengine.utils.getObservations
+import gov.cdc.prime.router.history.db.ReportGraph
+import gov.cdc.prime.router.metadata.LookupTable
+import gov.cdc.prime.router.report.ReportService
+import gov.cdc.prime.router.unittest.UnitTestUtils
+import io.mockk.every
+import io.mockk.mockkConstructor
+import io.mockk.mockkObject
+import io.mockk.unmockkAll
+import io.mockk.verify
+import org.apache.logging.log4j.kotlin.Logging
+import org.jooq.impl.DSL
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.testcontainers.junit.jupiter.Container
+import org.testcontainers.junit.jupiter.Testcontainers
+import java.io.File
+import java.util.UUID
+
+private const val VALID_FHIR_URL = "src/test/resources/fhirengine/engine/valid_data.fhir"
+
+private const val MULTIPLE_OBSERVATIONS_FHIR_URL =
+ "src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir"
+
+@Testcontainers
+@ExtendWith(ReportStreamTestDatabaseSetupExtension::class)
+class FHIRReceiverFilterIntegrationTests : Logging {
+
+ // Must have message ID, patient last name, patient first name, DOB, specimen type
+ // At least one of patient street, patient zip code, patient phone number, patient email
+ // At least one of order test date, specimen collection date/time, test result date
+ val fullElrQualityFilterSample: ReportStreamFilter = listOf(
+ "%messageId.exists()",
+ "%patient.name.family.exists()",
+ "%patient.name.given.count() > 0",
+ "%patient.birthDate.exists()",
+ "%specimen.type.exists()",
+ "(%patient.address.line.exists() or " +
+ "%patient.address.postalCode.exists() or " +
+ "%patient.telecom.exists())",
+ "(" +
+ "(%specimen.collection.collectedPeriod.exists() or " +
+ "%specimen.collection.collected.exists()" +
+ ") or " +
+ "%serviceRequest.occurrence.exists() or " +
+ "%observation.effective.exists())",
+ )
+
+ // requires only an id exists in the message header
+ val simpleElrQualifyFilter: ReportStreamFilter = listOf(
+ "Bundle.entry.resource.ofType(MessageHeader).id.exists()"
+ )
+
+ // Must have a processing mode set to production
+ val processingModeFilterProduction: ReportStreamFilter = listOf(
+ "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(" +
+ "system = 'http://terminology.hl7.org/CodeSystem/v2-0103'" +
+ ").code.exists() " +
+ "and " +
+ "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(" +
+ "system = 'http://terminology.hl7.org/CodeSystem/v2-0103'" +
+ ").code = 'P'"
+ )
+
+ // Must have a processing mode id of debugging
+ val processingModeFilterDebugging: ReportStreamFilter = listOf(
+ "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(" +
+ "system = 'http://terminology.hl7.org/CodeSystem/v2-0103'" +
+ ").code.exists() " +
+ "and " +
+ "Bundle.entry.resource.ofType(MessageHeader).meta.tag.where(" +
+ "system = 'http://terminology.hl7.org/CodeSystem/v2-0103'" +
+ ").code = 'D'"
+ )
+
+ // only allow observations that have 94558-5.
+ val conditionFilter: ReportStreamFilter = listOf(
+ "%resource.code.coding.code='94558-5'"
+ )
+ val noneConditionFilter: ReportStreamFilter = listOf(
+ "%resource.code.coding.code='1234'"
+ )
+
+ val noneMappedConditionFilter = listOf(CodeStringConditionFilter("foobar"))
+
+ val observationMappingMetadata = UnitTestUtils.simpleMetadata.apply {
+ this.lookupTableStore += mapOf(
+ "observation-mapping" to LookupTable(
+ "observation-mapping",
+ listOf(
+ listOf(
+ ObservationMappingConstants.TEST_CODE_KEY,
+ ObservationMappingConstants.CONDITION_CODE_KEY,
+ ObservationMappingConstants.CONDITION_CODE_SYSTEM_KEY,
+ ObservationMappingConstants.CONDITION_NAME_KEY
+ ),
+ listOf(
+ "94558-5",
+ "flu",
+ "SNOMEDCT",
+ "Influenza (disorder)"
+ )
+ )
+ )
+ )
+ }
+
+ @Container
+ val azuriteContainer = TestcontainersUtils.createAzuriteContainer(
+ customImageName = "azurite_fhirfunctionintegration1",
+ customEnv = mapOf(
+ "AZURITE_ACCOUNTS" to "devstoreaccount1:keydevstoreaccount1"
+ )
+ )
+
+ val azureEventService = LocalAzureEventServiceImpl()
+
+ @BeforeEach
+ fun beforeEach() {
+ mockkObject(QueueAccess)
+ every { QueueAccess.sendMessage(any(), any()) } returns Unit
+ mockkObject(BlobAccess)
+ every { BlobAccess getProperty "defaultBlobMetadata" } returns UniversalPipelineTestUtils
+ .getBlobContainerMetadata(azuriteContainer)
+ mockkObject(BlobAccess.BlobContainerMetadata)
+ every {
+ BlobAccess.BlobContainerMetadata.build(
+ any(),
+ any()
+ )
+ } returns UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ // TODO consider not mocking DatabaseLookupTableAccess
+ mockkConstructor(DatabaseLookupTableAccess::class)
+ }
+
+ @AfterEach
+ fun afterEach() {
+ unmockkAll()
+ azureEventService.events.clear()
+ }
+
+ fun createReceiverFilter(
+ azureEventService: AzureEventService,
+ org: DeepOrganization? = null,
+ ): FHIRReceiverFilter {
+ val settings = FileSettings().loadOrganizations(org ?: UniversalPipelineTestUtils.universalPipelineOrganization)
+ val metadata = UnitTestUtils.simpleMetadata
+ metadata.lookupTableStore += mapOf(
+ "observation-mapping" to LookupTable("observation-mapping", emptyList())
+ )
+ return FHIRReceiverFilter(
+ metadata,
+ settings,
+ reportService = ReportService(ReportGraph(ReportStreamTestDatabaseContainer.testDatabaseAccess)),
+ azureEventService = azureEventService
+ )
+ }
+
+ fun generateQueueMessage(
+ report: Report,
+ blobContents: String,
+ sender: Sender,
+ receiverName: String,
+ ): String {
+ return """
+ {
+ "type": "${TaskAction.receiver_filter.literal}",
+ "reportId": "${report.id}",
+ "blobURL": "${report.bodyURL}",
+ "digest": "${BlobAccess.digestToString(BlobAccess.sha256Digest(blobContents.toByteArray()))}",
+ "blobSubFolderName": "${sender.fullName}",
+ "topic": "${sender.topic.jsonVal}",
+ "receiverFullName": "$receiverName"
+ }
+ """.trimIndent()
+ }
+
+ @Test
+ fun `should send valid FHIR report filtered by condition filter`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ conditionFilter = conditionFilter
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(MULTIPLE_OBSERVATIONS_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ "phd.x"
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ val routedContents = String(
+ BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ )
+ val routedBundle = FhirTranscoder.decode(routedContents)
+
+ // check observations
+ assertThat(routedBundle.getObservations()).hasSize(1)
+ assertThat(routedBundle.getObservations().first().code.coding).hasSize(1)
+ assertThat(routedBundle.getObservations().first().code.coding.first().code).isEqualTo("94558-5")
+ val expectedBundle = FhirTranscoder.decode(reportContents).apply {
+ this.getObservations().forEach {
+ if (it.code.coding.first().code != "94558-5") {
+ this.deleteResource(it)
+ }
+ }
+ }
+ assertThat(FhirTranscoder.encode(expectedBundle)).isEqualTo(FhirTranscoder.encode(routedBundle))
+
+ // check queue message
+ val expectedRouteQueueMessage = FhirTranslateQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ Topic.FULL_ELR,
+ receiver.fullName
+ ).serialize()
+
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(elrTranslationQueueName, expectedRouteQueueMessage)
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualTo(
+ ReportRouteEvent(
+ routedReport.reportId,
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(routedBundle),
+ AzureEventUtils.getObservationSummaries(
+ bundle.getObservations().filter { it.code.coding.first().code != "94558-5" }
+ ),
+ routedContents.length,
+ AzureEventUtils.getIdentifier(routedBundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should send valid FHIR report with no condition related filtering`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "y",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ conditionFilter = listOf("true")
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(MULTIPLE_OBSERVATIONS_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ receiver.fullName
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1)
+ .single()
+ val routedContents = String(
+ BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ )
+ assertThat(routedContents).isEqualTo(reportContents)
+
+ // check queue message
+ val expectedQueueMessage = FhirTranslateQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ Topic.FULL_ELR,
+ receiver.fullName
+ ).serialize()
+
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(
+ elrTranslationQueueName,
+ expectedQueueMessage
+ )
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val routedBundle = FhirTranscoder.decode(routedContents)
+ assertThat(azureEventService.events.single()).isEqualTo(
+ ReportRouteEvent(
+ routedReport.reportId,
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(FhirTranscoder.decode(reportContents)),
+ emptyList(),
+ routedContents.length,
+ AzureEventUtils.getIdentifier(routedBundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should not send report fully pruned by condition filter`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ conditionFilter = noneConditionFilter
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(MULTIPLE_OBSERVATIONS_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ "phd.x"
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ // check terminated lineage
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ assertThat(routedReport.nextAction).isEqualTo(TaskAction.none)
+ assertThat(routedReport.bodyUrl).isNull()
+ assertThat(routedReport.schemaTopic).isEqualTo(Topic.FULL_ELR)
+ assertThat(routedReport.bodyFormat).isEqualTo("FHIR")
+ assertThat(routedReport.itemCount).isZero()
+
+ // check for no queue message
+ verify(exactly = 0) {
+ QueueAccess.sendMessage(any(), any())
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualToIgnoringGivenProperties(
+ ReceiverFilterFailedEvent(
+ UUID.randomUUID(), // ignored
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ noneConditionFilter,
+ ReportStreamFilterType.CONDITION_FILTER,
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ ),
+ ReceiverFilterFailedEvent::reportId
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should send valid FHIR report filtered by mapped condition filter`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ mappedConditionFilter = listOf(CodeStringConditionFilter("flu"))
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(MULTIPLE_OBSERVATIONS_FHIR_URL).readText()
+ val bundle = FhirTranscoder.decode(reportContents)
+ bundle.getObservations().forEach {
+ it.addMappedConditions(observationMappingMetadata)
+ }
+ val stampedReportContents = FhirTranscoder.encode(bundle)
+ val report = UniversalPipelineTestUtils.createReport(
+ stampedReportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ stampedReportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ "phd.x"
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ val routedContents = String(
+ BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ )
+ val routedBundle = FhirTranscoder.decode(routedContents)
+
+ // check observations
+ assertThat(routedBundle.getObservations()).hasSize(1)
+ assertThat(routedBundle.getObservations().first().code.coding).hasSize(1)
+ assertThat(routedBundle.getObservations().first().code.coding.first().code).isEqualTo("94558-5")
+ val expectedBundle = bundle.copy()
+ expectedBundle.getObservations().forEach {
+ if (it.code.coding.first().code != "94558-5") {
+ expectedBundle.deleteResource(it)
+ }
+ }
+ assertThat(FhirTranscoder.encode(expectedBundle)).isEqualTo(FhirTranscoder.encode(routedBundle))
+
+ // check queue message
+ val expectedRouteQueueMessage = FhirTranslateQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ Topic.FULL_ELR,
+ receiver.fullName
+ ).serialize()
+
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(elrTranslationQueueName, expectedRouteQueueMessage)
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualTo(
+ ReportRouteEvent(
+ routedReport.reportId,
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(routedBundle),
+ AzureEventUtils.getObservationSummaries(
+ bundle.getObservations().filter { it.code.coding.first().code != "94558-5" }
+ ),
+ routedContents.length,
+ AzureEventUtils.getIdentifier(routedBundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should not send report fully pruned by mapped condition filter`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ mappedConditionFilter = noneMappedConditionFilter
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(MULTIPLE_OBSERVATIONS_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ "phd.x"
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ assertThat(routedReport.nextAction).isEqualTo(TaskAction.none)
+ assertThat(routedReport.bodyUrl).isNull()
+ assertThat(routedReport.schemaTopic).isEqualTo(Topic.FULL_ELR)
+ assertThat(routedReport.bodyFormat).isEqualTo("FHIR")
+ assertThat(routedReport.itemCount).isZero()
+
+ // check queue message
+ verify(exactly = 0) {
+ QueueAccess.sendMessage(any(), any())
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualToIgnoringGivenProperties(
+ ReceiverFilterFailedEvent(
+ UUID.randomUUID(),
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ noneMappedConditionFilter.map { it.toString() },
+ ReportStreamFilterType.MAPPED_CONDITION_FILTER,
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ ),
+ ReceiverFilterFailedEvent::reportId
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should respect full quality filter and not send message`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ qualityFilter = fullElrQualityFilterSample
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = validFHIRRecord1
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ receiver.fullName
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check queue message
+ verify(exactly = 0) {
+ QueueAccess.sendMessage(elrTranslationQueueName, any())
+ }
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ // check for terminated lineage
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ assertThat(routedReport.nextAction).isEqualTo(TaskAction.none)
+ assertThat(routedReport.bodyUrl).isNull()
+ assertThat(routedReport.schemaTopic).isEqualTo(Topic.FULL_ELR)
+ assertThat(routedReport.bodyFormat).isEqualTo("FHIR")
+ assertThat(routedReport.itemCount).isZero()
+
+ // check filter logging
+ val actionLogRecords = DSL.using(txn)
+ .select(Tables.ACTION_LOG.asterisk())
+ .from(Tables.ACTION_LOG)
+ .fetchInto(ActionLog::class.java)
+
+ assertThat(actionLogRecords).hasSameSizeAs(fullElrQualityFilterSample)
+
+ actionLogRecords.forEachIndexed { index, actionLog ->
+ assertThat(actionLog.trackingId).isEqualTo(validFHIRRecord1Identifier)
+ assertThat(actionLog.detail).isInstanceOf()
+ .matchesPredicate {
+ it.filterType == ReportStreamFilterType.QUALITY_FILTER &&
+ it.filter == fullElrQualityFilterSample[index] &&
+ it.receiverName == receiver.name &&
+ it.receiverOrg == receiver.organizationName
+ }
+ }
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single()).isInstanceOf()
+ .isEqualToIgnoringGivenProperties(
+ ReceiverFilterFailedEvent(
+ UUID.randomUUID(), // ignored
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ fullElrQualityFilterSample,
+ ReportStreamFilterType.QUALITY_FILTER,
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ ),
+ ReceiverFilterFailedEvent::reportId
+ )
+ }
+
+ @Test
+ fun `should respect simple quality filter and send message`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ qualityFilter = simpleElrQualifyFilter
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.first()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(VALID_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ receiver.fullName
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ val routedContents = String(
+ BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ )
+ assertThat(routedContents).isEqualTo(reportContents)
+
+ // check queue message
+ val expectedRouteQueueMessage = FhirTranslateQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ Topic.FULL_ELR,
+ receiver.fullName
+ ).serialize()
+
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(
+ elrTranslationQueueName,
+ expectedRouteQueueMessage
+ )
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualTo(
+ ReportRouteEvent(
+ routedReport.reportId,
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ emptyList(),
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should respect processing mode filter and send message`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ processingModeFilter = processingModeFilterProduction
+ )
+ )
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(VALID_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ receiver.fullName
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ val routedBundle = BlobAccess.downloadBlobAsByteArray(
+ routedReport.bodyUrl,
+ UniversalPipelineTestUtils.getBlobContainerMetadata(azuriteContainer)
+ )
+ assertThat(String(routedBundle)).isEqualTo(reportContents)
+
+ // check queue message
+ val expectedRouteQueueMessage = FhirTranslateQueueMessage(
+ routedReport.reportId,
+ routedReport.bodyUrl,
+ BlobAccess.digestToString(routedReport.blobDigest),
+ "phd.fhir-elr-no-transform",
+ Topic.FULL_ELR,
+ "phd.x"
+ ).serialize()
+
+ verify(exactly = 1) {
+ QueueAccess.sendMessage(elrTranslationQueueName, expectedRouteQueueMessage)
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single())
+ .isInstanceOf()
+ .isEqualTo(
+ ReportRouteEvent(
+ routedReport.reportId,
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ emptyList(),
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ )
+ )
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+ }
+ }
+
+ @Test
+ fun `should respect processing mode filter and not send message`() {
+ // set up
+ val receiverSetupData = listOf(
+ UniversalPipelineTestUtils.ReceiverSetupData(
+ "x",
+ jurisdictionalFilter = listOf("true"),
+ qualityFilter = listOf("true"),
+ routingFilter = listOf("true"),
+ processingModeFilter = processingModeFilterDebugging,
+ )
+ )
+
+ val receivers = UniversalPipelineTestUtils.createReceivers(receiverSetupData)
+ val receiver = receivers.single()
+ val org = UniversalPipelineTestUtils.createOrganizationWithReceivers(receivers)
+ val receiverFilter = createReceiverFilter(azureEventService, org)
+ val reportContents = File(VALID_FHIR_URL).readText()
+ val report = UniversalPipelineTestUtils.createReport(
+ reportContents,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ azuriteContainer
+ )
+ val queueMessage = generateQueueMessage(
+ report,
+ reportContents,
+ UniversalPipelineTestUtils.fhirSenderWithNoTransform,
+ receiver.fullName
+ )
+ val fhirFunctions = UniversalPipelineTestUtils.createFHIRFunctionsInstance()
+
+ // execute
+ fhirFunctions.doReceiverFilter(queueMessage, 1, receiverFilter)
+
+ // check queue
+ verify(exactly = 0) {
+ QueueAccess.sendMessage(elrTranslationQueueName, any())
+ }
+
+ // check action table
+ UniversalPipelineTestUtils.checkActionTable(listOf(TaskAction.receive, TaskAction.receiver_filter))
+
+ // check results
+ ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
+ // check for terminated lineage
+ val routedReport = UniversalPipelineTestUtils.fetchChildReports(report, txn, 1).single()
+ assertThat(routedReport.nextAction).isEqualTo(TaskAction.none)
+ assertThat(routedReport.bodyUrl).isNull()
+ assertThat(routedReport.schemaTopic).isEqualTo(Topic.FULL_ELR)
+ assertThat(routedReport.bodyFormat).isEqualTo("FHIR")
+ assertThat(routedReport.itemCount).isZero()
+
+ // check filter logging
+ val actionLogRecords = DSL.using(txn)
+ .select(Tables.ACTION_LOG.asterisk())
+ .from(Tables.ACTION_LOG)
+ .fetchInto(ActionLog::class.java)
+
+ assertThat(actionLogRecords).hasSize(1)
+
+ with(actionLogRecords.single()) {
+ assertThat(this.trackingId).isEqualTo("MT_COCNB_ORU_NBPHELR.1.5348467")
+ assertThat(this.type).isEqualTo(ActionLogLevel.warning)
+ assertThat(this.scope).isEqualTo(ActionLogScope.item)
+ assertThat(this.index).isEqualTo(1)
+ assertThat(this.detail).isInstanceOf()
+ .matchesPredicate {
+ it.filter == processingModeFilterDebugging.single() &&
+ it.filterType == ReportStreamFilterType.PROCESSING_MODE_FILTER &&
+ it.receiverName == receiver.name &&
+ it.receiverOrg == receiver.organizationName
+ }
+ }
+ }
+
+ // check events
+ assertThat(azureEventService.events).hasSize(1)
+ val bundle = FhirTranscoder.decode(reportContents)
+ assertThat(azureEventService.events.single()).isInstanceOf()
+ .isEqualToIgnoringGivenProperties(
+ ReceiverFilterFailedEvent(
+ UUID.randomUUID(), // ignored
+ report.id,
+ report.id,
+ Topic.FULL_ELR,
+ "phd.Test Sender",
+ receiver.fullName,
+ AzureEventUtils.getObservationSummaries(bundle),
+ processingModeFilterDebugging,
+ ReportStreamFilterType.PROCESSING_MODE_FILTER,
+ reportContents.length,
+ AzureEventUtils.getIdentifier(bundle)
+ ),
+ ReceiverFilterFailedEvent::reportId
+ )
+ }
+}
\ No newline at end of file
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FHIRRouterIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FHIRRouterIntegrationTests.kt
index bbc25cc4f17..8edf1e5af70 100644
--- a/prime-router/src/test/kotlin/fhirengine/azure/FHIRRouterIntegrationTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FHIRRouterIntegrationTests.kt
@@ -74,6 +74,7 @@ private const val VALID_FHIR_URL = "src/test/resources/fhirengine/engine/valid_d
private const val MULTIPLE_OBSERVATIONS_FHIR_URL =
"src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir"
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
@Testcontainers
@ExtendWith(ReportStreamTestDatabaseSetupExtension::class)
class FHIRRouterIntegrationTests : Logging {
@@ -386,7 +387,6 @@ class FHIRRouterIntegrationTests : Logging {
val actionLogDetail: ActionLogDetail?,
)
- // TODO remove this and refactor tests expecting it ones issue 14450 has been addressed
// https://github.com/CDCgov/prime-reportstream/issues/14450
private val expectedButStrangeObservationFilterEntry = listOf(
ActionLogRecordContent(
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionIntegrationTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionIntegrationTests.kt
index 44d8358e581..ac1ed72bb23 100644
--- a/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionIntegrationTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionIntegrationTests.kt
@@ -46,7 +46,7 @@ import gov.cdc.prime.router.fhirengine.engine.FHIRConverter
import gov.cdc.prime.router.fhirengine.engine.FHIRRouter
import gov.cdc.prime.router.fhirengine.engine.FHIRTranslator
import gov.cdc.prime.router.fhirengine.engine.QueueMessage
-import gov.cdc.prime.router.fhirengine.engine.elrRoutingQueueName
+import gov.cdc.prime.router.fhirengine.engine.elrDestinationFilterQueueName
import gov.cdc.prime.router.fhirengine.engine.elrTranslationQueueName
import gov.cdc.prime.router.history.DetailedActionLog
import gov.cdc.prime.router.history.db.ReportGraph
@@ -72,6 +72,7 @@ import java.io.File
import java.nio.charset.Charset
import java.time.OffsetDateTime
import java.util.UUID
+import gov.cdc.prime.router.azure.db.tables.ReportFile as RF
private const val MULTIPLE_TARGETS_FHIR_PATH = "src/test/resources/fhirengine/engine/valid_data_multiple_targets.fhir"
@@ -227,6 +228,7 @@ NTE|1|L|Note
OBX|2|NM|35659-2^Age at specimen collection^LN^^^^2.71||24|a^year^UCUM^^^^2.1|||||F||||00Z0000042||||||||SA.OTCSelfReport^^^^^&2.16.840.1.113883.3.8589.4.1.152&ISO^XX^^^00Z0000042||||||QST
SPM|1|^dba7572cc6334f1ea0744c5f235c823e&MMTC.PROD&2.16.840.1.113883.3.8589.4.2.106.1&ISO||697989009^Anterior nares swab^SCT^^^^20200901|||||||||||||20240403120000-0400|20240403120000-0400"""
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
@Testcontainers
@ExtendWith(ReportStreamTestDatabaseSetupExtension::class)
class FhirFunctionIntegrationTests() {
@@ -408,15 +410,15 @@ class FhirFunctionIntegrationTests() {
val processTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(processTask.processedAt).isNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
- .where(Task.TASK.NEXT_ACTION.eq(TaskAction.route))
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ .where(Task.TASK.NEXT_ACTION.eq(TaskAction.destination_filter))
.fetchOneInto(Task.TASK)
- assertThat(routeTask).isNull()
+ assertThat(nextTask).isNull()
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
- .where(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION.eq(TaskAction.route))
- .fetchOneInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
+ .where(RF.REPORT_FILE.NEXT_ACTION.eq(TaskAction.destination_filter))
+ .fetchOneInto(RF.REPORT_FILE)
assertThat(convertReportFile).isNull()
}
verify(exactly = 0) {
@@ -487,19 +489,19 @@ class FhirFunctionIntegrationTests() {
val processTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(processTask.processedAt).isNotNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
- .where(Task.TASK.NEXT_ACTION.eq(TaskAction.route))
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ .where(Task.TASK.NEXT_ACTION.eq(TaskAction.destination_filter))
.fetchOneInto(Task.TASK)
- assertThat(routeTask).isNotNull()
+ assertThat(nextTask).isNotNull()
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
- .where(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION.eq(TaskAction.route))
- .fetchOneInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
+ .where(RF.REPORT_FILE.NEXT_ACTION.eq(TaskAction.destination_filter))
+ .fetchOneInto(RF.REPORT_FILE)
assertThat(convertReportFile).isNotNull()
}
verify(exactly = 1) {
- QueueAccess.sendMessage(elrRoutingQueueName, any())
+ QueueAccess.sendMessage(elrDestinationFilterQueueName, any())
BlobAccess.uploadBody(MimeFormat.FHIR, any(), any(), any(), any())
}
}
@@ -568,19 +570,19 @@ class FhirFunctionIntegrationTests() {
val processTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(processTask.processedAt).isNotNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
- .where(Task.TASK.NEXT_ACTION.eq(TaskAction.route))
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ .where(Task.TASK.NEXT_ACTION.eq(TaskAction.destination_filter))
.fetchInto(Task.TASK)
- assertThat(routeTask).hasSize(2)
+ assertThat(nextTask).hasSize(2)
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
- .where(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION.eq(TaskAction.route))
- .fetchInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
+ .where(RF.REPORT_FILE.NEXT_ACTION.eq(TaskAction.destination_filter))
+ .fetchInto(RF.REPORT_FILE)
assertThat(convertReportFile).hasSize(2)
}
verify(exactly = 2) {
- QueueAccess.sendMessage(elrRoutingQueueName, any())
+ QueueAccess.sendMessage(elrDestinationFilterQueueName, any())
}
verify(exactly = 1) {
BlobAccess.uploadBody(
@@ -679,20 +681,20 @@ class FhirFunctionIntegrationTests() {
val processTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(processTask.processedAt).isNotNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
- .where(Task.TASK.NEXT_ACTION.eq(TaskAction.route))
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ .where(Task.TASK.NEXT_ACTION.eq(TaskAction.destination_filter))
.fetchInto(Task.TASK)
- assertThat(routeTask).hasSize(2)
+ assertThat(nextTask).hasSize(2)
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
- .where(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION.eq(TaskAction.route))
- .fetchInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
+ .where(RF.REPORT_FILE.NEXT_ACTION.eq(TaskAction.destination_filter))
+ .fetchInto(RF.REPORT_FILE)
assertThat(convertReportFile).hasSize(2)
assertThat(actionLogger.errors).hasSize(2)
}
verify(exactly = 2) {
- QueueAccess.sendMessage(elrRoutingQueueName, any())
+ QueueAccess.sendMessage(elrDestinationFilterQueueName, any())
}
verify(exactly = 1) {
@@ -802,22 +804,22 @@ class FhirFunctionIntegrationTests() {
val processTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(processTask.processedAt).isNotNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
- .where(Task.TASK.NEXT_ACTION.eq(TaskAction.route))
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ .where(Task.TASK.NEXT_ACTION.eq(TaskAction.destination_filter))
.fetchInto(Task.TASK)
- assertThat(routeTask).hasSize(2)
+ assertThat(nextTask).hasSize(2)
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
- .where(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION.eq(TaskAction.route))
- .fetchInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
+ .where(RF.REPORT_FILE.NEXT_ACTION.eq(TaskAction.destination_filter))
+ .fetchInto(RF.REPORT_FILE)
assertThat(convertReportFile).hasSize(2)
// Expect two errors for the two badly formed bundles
assertThat(actionLogger.errors).hasSize(2)
}
verify(exactly = 2) {
- QueueAccess.sendMessage(elrRoutingQueueName, any())
+ QueueAccess.sendMessage(elrDestinationFilterQueueName, any())
BlobAccess.uploadBody(MimeFormat.FHIR, any(), any(), any(), any())
}
verify(exactly = 1) {
@@ -924,12 +926,12 @@ class FhirFunctionIntegrationTests() {
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
// Verify that there were two created reports from the 2 items that were parseable
- val routedReports = DSL
+ val resultingReports = DSL
.using(txn)
- .select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.`in`(
DSL
.select(
@@ -944,11 +946,11 @@ class FhirFunctionIntegrationTests() {
)
)
).fetchInto(ReportFile::class.java)
- assertThat(routedReports).hasSize(2)
+ assertThat(resultingReports).hasSize(2)
// Verify that the expected FHIR bundles were uploaded
val fhirBundles =
- routedReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
+ resultingReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
assertThat(fhirBundles).each {
it.matchesPredicate { bytes ->
val invalidHL7Result = CompareData().compare(
@@ -1076,12 +1078,12 @@ class FhirFunctionIntegrationTests() {
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
// Verify that there were two created reports from the 2 items that were parseable
- val routedReports = DSL
+ val resultingReports = DSL
.using(txn)
- .select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.`in`(
DSL
.select(
@@ -1096,11 +1098,11 @@ class FhirFunctionIntegrationTests() {
)
)
).fetchInto(ReportFile::class.java)
- assertThat(routedReports).hasSize(2)
+ assertThat(resultingReports).hasSize(2)
// Verify that the expected FHIR bundles were uploaded
val fhirBundles =
- routedReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
+ resultingReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
.map { it.toString(Charset.defaultCharset()) }
assertThat(fhirBundles).containsOnly(validFHIRRecord1, validFHIRRecord2)
@@ -1212,12 +1214,12 @@ class FhirFunctionIntegrationTests() {
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
// Verify that there were two created reports from the 2 items that were parseable
- val routedReports = DSL
+ val resultingReports = DSL
.using(txn)
- .select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.`in`(
DSL
.select(
@@ -1232,11 +1234,11 @@ class FhirFunctionIntegrationTests() {
)
)
).fetchInto(ReportFile::class.java)
- assertThat(routedReports).hasSize(1)
+ assertThat(resultingReports).hasSize(1)
// Verify that the expected FHIR bundles were uploaded
val fhirBundles =
- routedReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
+ resultingReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
assertThat(fhirBundles).each {
it.matchesPredicate { bytes ->
val nistELRResult = CompareData().compare(
@@ -1346,12 +1348,12 @@ class FhirFunctionIntegrationTests() {
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
// Verify that there were two created reports from the 2 items that were parseable
- val routedReports = DSL
+ val resultingReports = DSL
.using(txn)
- .select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.`in`(
DSL
.select(
@@ -1366,11 +1368,11 @@ class FhirFunctionIntegrationTests() {
)
)
).fetchInto(ReportFile::class.java)
- assertThat(routedReports).hasSize(1)
+ assertThat(resultingReports).hasSize(1)
// Verify that the expected FHIR bundles were uploaded
val fhirBundles =
- routedReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
+ resultingReports.map { BlobAccess.downloadBlobAsByteArray(it.bodyUrl, blobContainerMetadata) }
assertThat(fhirBundles).each {
it.matchesPredicate { bytes ->
val radxMarsResult = CompareData().compare(
@@ -1398,6 +1400,7 @@ class FhirFunctionIntegrationTests() {
}
}
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
@Test
fun `test successfully processes a route message`() {
val report = seedTask(
@@ -1465,18 +1468,18 @@ class FhirFunctionIntegrationTests() {
val convertTask = ReportStreamTestDatabaseContainer.testDatabaseAccess.fetchTask(report.id)
assertThat(convertTask.routedAt).isNotNull()
ReportStreamTestDatabaseContainer.testDatabaseAccess.transact { txn ->
- val routeTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
+ val nextTask = DSL.using(txn).select(Task.TASK.asterisk()).from(Task.TASK)
.where(Task.TASK.NEXT_ACTION.eq(TaskAction.translate))
.fetchOneInto(Task.TASK)
- assertThat(routeTask).isNotNull()
+ assertThat(nextTask).isNotNull()
val convertReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.NEXT_ACTION
+ RF.REPORT_FILE.NEXT_ACTION
.eq(TaskAction.translate)
)
- .fetchOneInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .fetchOneInto(RF.REPORT_FILE)
assertThat(convertReportFile).isNotNull()
}
verify(exactly = 1) {
@@ -1529,9 +1532,9 @@ class FhirFunctionIntegrationTests() {
100,
oneOrganization
)
- val routeReport = seedTask(
+ val receiverFilteredReport = seedTask(
MimeFormat.FHIR,
- TaskAction.route,
+ TaskAction.receiver_filter,
TaskAction.translate,
Event.EventAction.TRANSLATE,
Topic.ELR_ELIMS,
@@ -1539,15 +1542,25 @@ class FhirFunctionIntegrationTests() {
oneOrganization,
translateReport
)
+ val destinationFilteredReport = seedTask(
+ MimeFormat.FHIR,
+ TaskAction.destination_filter,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ Topic.ELR_ELIMS,
+ 98,
+ oneOrganization,
+ receiverFilteredReport
+ )
val convertReport = seedTask(
MimeFormat.FHIR,
TaskAction.convert,
- TaskAction.route,
- Event.EventAction.ROUTE,
+ TaskAction.destination_filter,
+ Event.EventAction.DESTINATION_FILTER,
Topic.ELR_ELIMS,
- 98,
+ 97,
oneOrganization,
- routeReport
+ destinationFilteredReport
)
val receiveReport = seedTask(
MimeFormat.FHIR,
@@ -1555,7 +1568,7 @@ class FhirFunctionIntegrationTests() {
TaskAction.convert,
Event.EventAction.CONVERT,
Topic.ELR_ELIMS,
- 97,
+ 96,
oneOrganization,
convertReport,
receiveBlobUrl
@@ -1617,13 +1630,13 @@ class FhirFunctionIntegrationTests() {
assertThat(queueTask).isNotNull()
val sendReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.eq(queueTask!!.reportId)
)
- .fetchOneInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .fetchOneInto(RF.REPORT_FILE)
assertThat(sendReportFile).isNotNull()
// verify sendReportFile message does not match the original message from receive step
@@ -1693,9 +1706,9 @@ class FhirFunctionIntegrationTests() {
100,
oneOrganization
)
- val routeReport = seedTask(
+ val receiverFilteredReport = seedTask(
MimeFormat.FHIR,
- TaskAction.route,
+ TaskAction.receiver_filter,
TaskAction.translate,
Event.EventAction.TRANSLATE,
Topic.ELR_ELIMS,
@@ -1703,15 +1716,25 @@ class FhirFunctionIntegrationTests() {
oneOrganization,
translateReport
)
+ val destinationFilteredReport = seedTask(
+ MimeFormat.FHIR,
+ TaskAction.destination_filter,
+ TaskAction.receiver_filter,
+ Event.EventAction.RECEIVER_FILTER,
+ Topic.ELR_ELIMS,
+ 98,
+ oneOrganization,
+ receiverFilteredReport
+ )
val convertReport = seedTask(
MimeFormat.FHIR,
TaskAction.convert,
TaskAction.route,
Event.EventAction.ROUTE,
Topic.ELR_ELIMS,
- 98,
+ 97,
oneOrganization,
- routeReport
+ destinationFilteredReport
)
val receiveReport = seedTask(
MimeFormat.FHIR,
@@ -1719,7 +1742,7 @@ class FhirFunctionIntegrationTests() {
TaskAction.convert,
Event.EventAction.CONVERT,
Topic.ELR_ELIMS,
- 97,
+ 96,
oneOrganization,
convertReport,
receiveBlobUrl
@@ -1776,13 +1799,13 @@ class FhirFunctionIntegrationTests() {
assertThat(sendTask).isNotNull()
val sendReportFile =
- DSL.using(txn).select(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.asterisk())
- .from(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ DSL.using(txn).select(RF.REPORT_FILE.asterisk())
+ .from(RF.REPORT_FILE)
.where(
- gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE.REPORT_ID
+ RF.REPORT_FILE.REPORT_ID
.eq(sendTask!!.reportId)
)
- .fetchOneInto(gov.cdc.prime.router.azure.db.tables.ReportFile.REPORT_FILE)
+ .fetchOneInto(RF.REPORT_FILE)
assertThat(sendReportFile).isNotNull()
// verify sendReportFile message matches the original message from receive step
diff --git a/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionTests.kt b/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionTests.kt
index 67f5a8178bb..5a67e29f6d8 100644
--- a/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/azure/FhirFunctionTests.kt
@@ -25,17 +25,15 @@ import gov.cdc.prime.router.fhirengine.engine.FHIRConverter
import gov.cdc.prime.router.fhirengine.engine.FHIREngine
import gov.cdc.prime.router.fhirengine.engine.FHIRRouter
import gov.cdc.prime.router.fhirengine.engine.FHIRTranslator
-import gov.cdc.prime.router.fhirengine.engine.FhirConvertQueueMessage
+import gov.cdc.prime.router.fhirengine.engine.FhirDestinationFilterQueueMessage
import gov.cdc.prime.router.fhirengine.engine.FhirRouteQueueMessage
import gov.cdc.prime.router.fhirengine.engine.QueueMessage
-import gov.cdc.prime.router.fhirengine.engine.elrRoutingQueueName
+import gov.cdc.prime.router.fhirengine.engine.elrDestinationFilterQueueName
import gov.cdc.prime.router.fhirengine.engine.elrTranslationQueueName
import gov.cdc.prime.router.metadata.LookupTable
-import gov.cdc.prime.router.report.ReportService
import gov.cdc.prime.router.unittest.UnitTestUtils
import io.mockk.clearAllMocks
import io.mockk.every
-import io.mockk.mockk
import io.mockk.mockkClass
import io.mockk.mockkObject
import io.mockk.spyk
@@ -47,6 +45,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.OffsetDateTime
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
class FhirFunctionTests {
val dataProvider = MockDataProvider { emptyArray() }
val connection = MockConnection(dataProvider)
@@ -54,7 +53,6 @@ class FhirFunctionTests {
val blobMock = mockkClass(BlobAccess::class)
val queueMock = mockkClass(QueueAccess::class)
val timing1 = mockkClass(Receiver.Timing::class)
- val reportServiceMock = mockk()
val oneOrganization = DeepOrganization(
"phd", "test", Organization.Jurisdiction.FEDERAL,
@@ -154,13 +152,13 @@ class FhirFunctionTests {
topic = Topic.FULL_ELR,
)
val routeEvent = ProcessEvent(
- Event.EventAction.ROUTE,
+ Event.EventAction.DESTINATION_FILTER,
report.id,
Options.None,
emptyMap(),
emptyList()
)
- val message = FhirConvertQueueMessage(
+ val message = FhirDestinationFilterQueueMessage(
report.id,
"",
"BlobAccess.digestToString(blobInfo.digest)",
@@ -191,11 +189,12 @@ class FhirFunctionTests {
fhirEngine.doWork(any(), any(), any())
actionHistory.trackActionParams(queueMessage) // string
actionHistory.trackLogs(emptyList()) // list actionLog
- queueMock.sendMessage(elrRoutingQueueName, message.serialize())
+ queueMock.sendMessage(elrDestinationFilterQueueName, message.serialize())
workflowEngine.recordAction(any(), any())
}
}
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
// test route-fhir
@Test
fun `test route-fhir`() {
diff --git a/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/DefaultFilterTests.kt b/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/DefaultFilterTests.kt
index daa1e0c700f..59c5d752407 100644
--- a/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/DefaultFilterTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/DefaultFilterTests.kt
@@ -39,6 +39,7 @@ import java.io.ByteArrayInputStream
import java.io.File
import kotlin.test.Test
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DefaultFilterTests {
val dataProvider = MockDataProvider { emptyArray() }
diff --git a/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/GetFilterTests.kt b/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/GetFilterTests.kt
index 9b26a09b910..c2611d685e6 100644
--- a/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/GetFilterTests.kt
+++ b/prime-router/src/test/kotlin/fhirengine/engine/fhirRouterTests/GetFilterTests.kt
@@ -34,6 +34,7 @@ import kotlin.test.assertTrue
private const val ORGANIZATION_NAME = "co-phd"
private const val RECEIVER_NAME = "full-elr-hl7"
+// TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class GetFilterTests {
val dataProvider = MockDataProvider { emptyArray() }
diff --git a/prime-router/src/test/kotlin/history/SubmissionHistoryTests.kt b/prime-router/src/test/kotlin/history/SubmissionHistoryTests.kt
index 151f48b9b88..28326251f59 100644
--- a/prime-router/src/test/kotlin/history/SubmissionHistoryTests.kt
+++ b/prime-router/src/test/kotlin/history/SubmissionHistoryTests.kt
@@ -444,7 +444,7 @@ class SubmissionHistoryTests {
val refUUID = UUID.randomUUID()
- var reports = listOf(
+ val reports = listOf(
inputReport,
DetailedReport(
refUUID, "recvOrg1",
@@ -585,6 +585,116 @@ class SubmissionHistoryTests {
@Test
fun `test DetailedSubmissionHistory UP overallStatus (received)`() {
+ // received: freshly received, no routing yet
+ val testReceived = DetailedSubmissionHistory(
+ 1,
+ TaskAction.receive,
+ OffsetDateTime.now(),
+ HttpStatus.OK.value(),
+ reports = mutableListOf(),
+ logs = emptyList()
+ )
+ testReceived.actionsPerformed = mutableSetOf(TaskAction.receive)
+ testReceived.enrichWithSummary()
+ testReceived.run {
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ }
+ // received: no destinations have been calculated yet
+ val noDestinationsCalculatedYet = emptyList().toMutableList()
+ val testReceivedButNoDestinationsYet = DetailedSubmissionHistory(
+ 1, TaskAction.destination_filter, OffsetDateTime.now(),
+ HttpStatus.OK.value(), noDestinationsCalculatedYet, logs = emptyList()
+ )
+ testReceivedButNoDestinationsYet.enrichWithSummary()
+ testReceivedButNoDestinationsYet.run {
+ assertThat(destinations.count()).isEqualTo(0)
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ }
+ val inputReport = DetailedReport(
+ UUID.randomUUID(),
+ null,
+ null,
+ "org",
+ "client",
+ Topic.FULL_ELR,
+ "externalName",
+ null,
+ null,
+ 5,
+ null,
+ false,
+ null,
+ null,
+ null
+ )
+ // received: one of two destinations has been calculated, with all items for it filtered out
+ val oneFilteredDestinationCalculated = listOf(
+ inputReport,
+ DetailedReport(
+ UUID.randomUUID(),
+ "recvOrg4",
+ "recvSvc4",
+ null,
+ null,
+ Topic.FULL_ELR,
+ "one item dest",
+ null,
+ OffsetDateTime.now().plusDays(1),
+ 0,
+ 5,
+ true,
+ null,
+ null,
+ null
+ ),
+ ).toMutableList()
+ val testReceivedOneFilteredDestination = DetailedSubmissionHistory(
+ 1, TaskAction.destination_filter, OffsetDateTime.now(),
+ HttpStatus.OK.value(), oneFilteredDestinationCalculated, logs = emptyList()
+ )
+ testReceivedOneFilteredDestination.enrichWithSummary()
+ testReceivedOneFilteredDestination.run {
+ assertThat(destinations.count()).isEqualTo(1)
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ }
+ // received: one of two destinations has been calculated, with no items filtered out
+ val reports = listOf(
+ DetailedReport(
+ UUID.randomUUID(),
+ null,
+ null,
+ null,
+ null,
+ Topic.FULL_ELR,
+ "no item count dest",
+ null,
+ null,
+ 0,
+ null,
+ true,
+ null,
+ null,
+ null
+ ),
+ ).toMutableList()
+ val testReceivedNoDestination = DetailedSubmissionHistory(
+ 1,
+ TaskAction.destination_filter,
+ OffsetDateTime.now(),
+ HttpStatus.OK.value(),
+ reports,
+ emptyList()
+ )
+ testReceivedNoDestination.enrichWithSummary()
+ testReceivedNoDestination.run {
+ assertThat(destinationCount).isEqualTo(0)
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ }
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `test DetailedSubmissionHistory UP overallStatus (received) (legacy route step)`() {
// received: freshly received, no routing yet
val testReceived = DetailedSubmissionHistory(
1,
@@ -710,6 +820,61 @@ class SubmissionHistoryTests {
assertThat(actualCompletionAt).isNull()
}
+ val reports = listOf(
+ DetailedReport(
+ UUID.randomUUID(),
+ null,
+ null,
+ null,
+ null,
+ Topic.TEST,
+ "no item count dest",
+ null,
+ null,
+ 0,
+ null,
+ true,
+ null,
+ null,
+ null
+ ),
+ ).toMutableList()
+ val testReceivedNoDestination = DetailedSubmissionHistory(
+ 1,
+ TaskAction.destination_filter,
+ OffsetDateTime.now(),
+ HttpStatus.OK.value(),
+ reports,
+ logs = emptyList()
+ )
+ testReceivedNoDestination.enrichWithSummary()
+ testReceivedNoDestination.run {
+ assertThat(destinationCount).isEqualTo(0)
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ assertThat(plannedCompletionAt).isNull()
+ assertThat(actualCompletionAt).isNull()
+ }
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `test DetailedSubmissionHistory LEGACY overallStatus calculations (received) (legacy route step)`() {
+ val testReceived = DetailedSubmissionHistory(
+ 1,
+ TaskAction.receive,
+ OffsetDateTime.now(),
+ HttpStatus.OK.value(),
+ reports = mutableListOf(),
+ logs = emptyList()
+ )
+ testReceived.actionsPerformed = mutableSetOf(TaskAction.receive)
+ testReceived.enrichWithSummary()
+ testReceived.run {
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.RECEIVED)
+ assertThat(plannedCompletionAt).isNull()
+ assertThat(actualCompletionAt).isNull()
+ }
+
val reports = listOf(
DetailedReport(
UUID.randomUUID(),
@@ -748,6 +913,120 @@ class SubmissionHistoryTests {
@Test
fun `test DetailedSubmissionHistory overallStatus (waiting to deliver)`() {
+ val inputReport = DetailedReport(
+ UUID.randomUUID(),
+ null,
+ null,
+ "org",
+ "client",
+ Topic.FULL_ELR,
+ "externalName",
+ null,
+ null,
+ 5,
+ null,
+ false,
+ null,
+ null,
+ null
+ )
+ val latestReport = DetailedReport(
+ UUID.randomUUID(),
+ "recvOrg2",
+ "recvSvc2",
+ null, null,
+ Topic.FULL_ELR,
+ "otherExternalName2",
+ null,
+ null,
+ 4,
+ null,
+ true,
+ null,
+ null,
+ null
+ )
+ // waiting to deliver: one of two destinations has been calculated, with no items filtered out
+ val oneUnfilteredDestinationCalculated = listOf(
+ inputReport,
+ DetailedReport(
+ UUID.randomUUID(),
+ "recvOrg4",
+ "recvSvc4",
+ null,
+ null,
+ Topic.FULL_ELR,
+ "one item dest",
+ null,
+ null,
+ 5,
+ 5,
+ true,
+ null,
+ null,
+ null
+ ),
+ ).toMutableList()
+ val testReceivedOneUnfilteredDestination = DetailedSubmissionHistory(
+ 1, TaskAction.destination_filter, OffsetDateTime.now(),
+ HttpStatus.OK.value(), oneUnfilteredDestinationCalculated, logs = emptyList()
+ )
+ testReceivedOneUnfilteredDestination.enrichWithSummary()
+ testReceivedOneUnfilteredDestination.run {
+ assertThat(destinations.count()).isEqualTo(1)
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.WAITING_TO_DELIVER)
+ }
+ val reports = listOf(
+ inputReport,
+ latestReport,
+ DetailedReport(
+ UUID.randomUUID(),
+ "recvOrg1",
+ "recvSvc1",
+ null,
+ null,
+ Topic.FULL_ELR,
+ "otherExternalName1",
+ null,
+ null,
+ 1,
+ null,
+ true,
+ null,
+ null,
+ null
+ ),
+ DetailedReport(
+ UUID.randomUUID(),
+ "recvOrg3",
+ "recvSvc3",
+ null,
+ null,
+ Topic.FULL_ELR,
+ "no item count dest",
+ null,
+ null,
+ 0,
+ null,
+ true,
+ null,
+ null,
+ null
+ ),
+ ).toMutableList()
+ val testWaitingToDeliver = DetailedSubmissionHistory(
+ 1, TaskAction.receive, OffsetDateTime.now(),
+ HttpStatus.OK.value(), reports, logs = emptyList()
+ )
+ testWaitingToDeliver.enrichWithSummary()
+ testWaitingToDeliver.run {
+ assertThat(overallStatus).isEqualTo(DetailedSubmissionHistory.Status.WAITING_TO_DELIVER)
+ }
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `test DetailedSubmissionHistory overallStatus (waiting to deliver) (legacy route step)`() {
val inputReport = DetailedReport(
UUID.randomUUID(),
null,
diff --git a/prime-router/src/test/kotlin/history/azure/SubmissionFunctionIntegrationTests.kt b/prime-router/src/test/kotlin/history/azure/SubmissionFunctionIntegrationTests.kt
index c215ad78dda..87336aa2d5d 100644
--- a/prime-router/src/test/kotlin/history/azure/SubmissionFunctionIntegrationTests.kt
+++ b/prime-router/src/test/kotlin/history/azure/SubmissionFunctionIntegrationTests.kt
@@ -252,6 +252,64 @@ class SubmissionFunctionIntegrationTests {
@Test
fun `it should return a history for partially delivered submission`() {
+ val submittedReport = reportGraph {
+ topic(Topic.FULL_ELR)
+ format(MimeFormat.HL7)
+ sender(UniversalPipelineTestUtils.hl7Sender)
+
+ submission {
+ action(TaskAction.receive)
+ reportGraphNode {
+ action(TaskAction.convert)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.warning))
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ itemCount(0)
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ itemCount(0)
+ }
+ }
+ }
+ }
+ }.generate(ReportStreamTestDatabaseContainer.testDatabaseAccess)
+
+ val httpRequestMessage = MockHttpRequestMessage()
+
+ val func = setupSubmissionFunction()
+
+ val history = func
+ .getReportDetailedHistory(httpRequestMessage, submittedReport.node.reportId.toString())
+ assertThat(history).isNotNull()
+ val historyNode = JacksonMapperUtilities.defaultMapper.readTree(history.body.toString())
+ assertThat(
+ historyNode.get("overallStatus").asText()
+ ).isEqualTo(DetailedSubmissionHistory.Status.PARTIALLY_DELIVERED.toString())
+ assertThat(historyNode.get("destinations").size()).isEqualTo(2)
+ assertThat(historyNode.get("errors").size()).isEqualTo(0)
+ assertThat(historyNode.get("warnings").size()).isEqualTo(1)
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `it should return a history for partially delivered submission (for legacy route step)`() {
val submittedReport = reportGraph {
topic(Topic.FULL_ELR)
format(MimeFormat.HL7)
@@ -337,6 +395,42 @@ class SubmissionFunctionIntegrationTests {
@Test
fun `it should return a history that indicates the report is not going to be delivered`() {
+ val submittedReport = reportGraph {
+ topic(Topic.FULL_ELR)
+ format(MimeFormat.HL7)
+ sender(UniversalPipelineTestUtils.hl7Sender)
+
+ submission {
+ action(TaskAction.receive)
+ reportGraphNode {
+ action(TaskAction.convert)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.warning))
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ }
+ }
+ }
+ }.generate(ReportStreamTestDatabaseContainer.testDatabaseAccess)
+
+ val httpRequestMessage = MockHttpRequestMessage()
+
+ val func = setupSubmissionFunction()
+
+ val history = func
+ .getReportDetailedHistory(httpRequestMessage, submittedReport.node.reportId.toString())
+ assertThat(history).isNotNull()
+ val historyNode = JacksonMapperUtilities.defaultMapper.readTree(history.body.toString())
+ assertThat(
+ historyNode.get("overallStatus").asText()
+ ).isEqualTo(DetailedSubmissionHistory.Status.NOT_DELIVERING.toString())
+ assertThat(historyNode.get("destinations").size()).isEqualTo(0)
+ assertThat(historyNode.get("errors").size()).isEqualTo(0)
+ assertThat(historyNode.get("warnings").size()).isEqualTo(1)
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `it should return a history that indicates the report is not going to be delivered (legacy route step)`() {
val submittedReport = reportGraph {
topic(Topic.FULL_ELR)
format(MimeFormat.HL7)
@@ -372,6 +466,72 @@ class SubmissionFunctionIntegrationTests {
@Test
fun `it should return a history that indicates waiting to deliver`() {
+ val submittedReport = reportGraph {
+ topic(Topic.FULL_ELR)
+ format(MimeFormat.HL7)
+ sender(UniversalPipelineTestUtils.hl7Sender)
+
+ submission {
+ action(TaskAction.receive)
+ reportGraphNode {
+ action(TaskAction.convert)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.warning))
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.error))
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ }
+ }
+ }
+ }
+ }.generate(ReportStreamTestDatabaseContainer.testDatabaseAccess)
+
+ val httpRequestMessage = MockHttpRequestMessage()
+
+ val func = setupSubmissionFunction()
+
+ val history = func
+ .getReportDetailedHistory(httpRequestMessage, submittedReport.node.reportId.toString())
+ assertThat(history).isNotNull()
+ val historyNode = JacksonMapperUtilities.defaultMapper.readTree(history.body.toString())
+ assertThat(
+ historyNode.get("overallStatus").asText()
+ ).isEqualTo(DetailedSubmissionHistory.Status.WAITING_TO_DELIVER.toString())
+ assertThat(historyNode.get("destinations").size()).isEqualTo(2)
+ assertThat(historyNode.get("errors").size()).isEqualTo(1)
+ assertThat(historyNode.get("warnings").size()).isEqualTo(1)
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `it should return a history that indicates waiting to deliver (legacy route step)`() {
val submittedReport = reportGraph {
topic(Topic.FULL_ELR)
format(MimeFormat.HL7)
@@ -437,6 +597,84 @@ class SubmissionFunctionIntegrationTests {
@Test
fun `it should return history of a submission that is delivered`() {
+ val submittedReport = reportGraph {
+ topic(Topic.FULL_ELR)
+ format(MimeFormat.HL7)
+ sender(UniversalPipelineTestUtils.hl7Sender)
+
+ submission {
+ action(TaskAction.receive)
+ reportGraphNode {
+ action(TaskAction.convert)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.warning))
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ log(ActionLog(InvalidParamMessage("log"), type = ActionLogLevel.error))
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ }
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.destination_filter)
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[0])
+ }
+ }
+ reportGraphNode {
+ action(TaskAction.translate)
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ reportGraphNode {
+ action(TaskAction.send)
+ transportResult("Success")
+ receiver(UniversalPipelineTestUtils.universalPipelineOrganization.receivers[1])
+ }
+ }
+ }
+ }
+ }
+ }.generate(ReportStreamTestDatabaseContainer.testDatabaseAccess)
+
+ val httpRequestMessage = MockHttpRequestMessage()
+
+ val func = setupSubmissionFunction()
+
+ val history = func
+ .getReportDetailedHistory(httpRequestMessage, submittedReport.node.reportId.toString())
+ assertThat(history).isNotNull()
+ val historyNode = JacksonMapperUtilities.defaultMapper.readTree(history.body.toString())
+ assertThat(
+ historyNode.get("overallStatus").asText()
+ ).isEqualTo(DetailedSubmissionHistory.Status.DELIVERED.toString())
+ assertThat(historyNode.get("destinations").size()).isEqualTo(2)
+ assertThat(historyNode.get("errors").size()).isEqualTo(1)
+ assertThat(historyNode.get("warnings").size()).isEqualTo(1)
+ assertThat(historyNode.get("sender").asText()).isEqualTo("phd.elr-hl7-sender")
+ assertThat(historyNode.get("actualCompletionAt").asText()).isNotNull()
+ }
+
+ // TODO: remove after route queue empty (see https://github.com/CDCgov/prime-reportstream/issues/15039)
+ @Test
+ fun `it should return history of a submission that is delivered (legacy route step)`() {
val submittedReport = reportGraph {
topic(Topic.FULL_ELR)
format(MimeFormat.HL7)
diff --git a/prime-router/src/test/kotlin/report/ReportServiceTests.kt b/prime-router/src/test/kotlin/report/ReportServiceTests.kt
index a25569b1f0d..9bdd4620441 100644
--- a/prime-router/src/test/kotlin/report/ReportServiceTests.kt
+++ b/prime-router/src/test/kotlin/report/ReportServiceTests.kt
@@ -36,14 +36,14 @@ class ReportServiceTests {
}
@Test
- fun `getRootReport failure`() {
+ fun `getRootReport on root report returns root report`() {
val f = Fixture()
+ val report = mockk()
every { f.reportGraphMock.getRootReport(f.childReportId) } returns null
+ every { f.reportGraphMock.db.fetchReportFile(f.childReportId) } returns report
- assertFailure {
- f.reportService.getRootReport(f.childReportId)
- }.isInstanceOf(IllegalStateException::class)
+ assertThat(f.reportService.getRootReport(f.childReportId)).isEqualTo(report)
}
@Test
diff --git a/prime-router/src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir b/prime-router/src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir
index b1578713e85..e7d8f3979fd 100644
--- a/prime-router/src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir
+++ b/prime-router/src/test/resources/fhirengine/engine/bundle_multiple_observations.fhir
@@ -1 +1 @@
-{ "resourceType": "Bundle", "id": "1667861767830636000.7db38d22-b713-49fc-abfa-2edba9c12347", "meta": { "lastUpdated": "2022-11-07T22:56:07.832+00:00" }, "identifier": { "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, "type": "message", "timestamp": "2021-08-03T13:15:11.015+00:00", "entry": [ { "fullUrl": "MessageHeader/c03f1b6b-cfc3-3477-89c0-d38316cd1a38", "resource": { "resourceType": "MessageHeader", "id": "c03f1b6b-cfc3-3477-89c0-d38316cd1a38", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "eventCoding": { "system": "http://terminology.hl7.org/CodeSystem/v2-0003", "code": "R01", "display": "ORU/ACK - Unsolicited transmission of an observation message" }, "destination": [ { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/facility-identifier", "valueOid": "urn:oid:2.16.840.1.114222.4.1.214104" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/application-identifier", "valueOid": "urn:oid:2.16.840.1.114222.4.3.3.10.1.1" } ], "name": "CDPH CA REDIE", "endpoint": "CDPH_CID" } ], "sender": { "reference": "Organization/1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64" }, "source": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/software-vendor-org", "valueReference": { "reference": "Organization/1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2" } }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/software-install-date", "valueDateTime": "2021-07-26" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/software-binary-id", "valueString": "0.1-SNAPSHOT" } ], "name": "CDC PRIME - Atlanta,", "software": "PRIME Data Hub", "version": "0.1-SNAPSHOT", "endpoint": "urn:oid:2.16.840.1.114222.4.1.237821" } } }, { "fullUrl": "Organization/1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64", "resource": { "resourceType": "Organization", "id": "1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "urn:oid:05D2222542" } ], "name": "Winchester House", "address": [ { "country": "USA" } ] } }, { "fullUrl": "Organization/1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2", "resource": { "resourceType": "Organization", "id": "1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "name": "Centers for Disease Control and Prevention" } }, { "fullUrl": "Provenance/1667861767909887000.56c99862-7264-4c8f-9d5e-896ac70a0d75", "resource": { "resourceType": "Provenance", "id": "1667861767909887000.56c99862-7264-4c8f-9d5e-896ac70a0d75", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "recorded": "2021-08-03T13:15:11.0147Z", "activity": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0003", "code": "R01", "display": "ORU_R01" } ] }, "agent": [ { "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/provenance-participant-type", "code": "author" } ] }, "who": { "reference": "Organization/1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1" } } ], "entity": [ { "role": "source", "what": { "reference": "Device/1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea" } } ] } }, { "fullUrl": "Organization/1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1", "resource": { "resourceType": "Organization", "id": "1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "name": "Winchester House", "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Device/1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea", "resource": { "resourceType": "Device", "id": "1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "type": { "coding": [ { "system": "http://terminology.hl7.org/NamingSystem/uri", "code": "2.16.840.1.114222.4.1.237821", "display": "CDC PRIME - Atlanta," } ] } } ], "deviceName": [ { "name": "CDC PRIME - Atlanta,", "type": "user-friendly-name" } ] } }, { "fullUrl": "Observation/1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f", "resource": { "resourceType": "Observation", "id": "1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id", "valueReference": { "reference": "Organization/1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039" } } ], "identifier": [ { "system": "urn:id:extID", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "94558-5" } ], "text": "SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectiveDateTime": "2021-08-02T00:00:00-05:00", "issued": "2021-08-02T00:00:00-05:00", "performer": [ { "reference": "Organization/1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027" } ], "valueCodeableConcept": { "coding": [ { "system": "http://snomed.info/sct", "code": "260415000" } ], "text": "Not detected" }, "interpretation": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0078", "code": "N", "display": "Normal" } ] } ], "method": { "coding": [ { "system": "99ELR", "code": "10811877011290_DIT" } ], "text": "10811877011290" } } }, { "fullUrl": "Organization/1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027", "resource": { "resourceType": "Organization", "id": "1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:2.16.840.1.113883.19.4.6" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", "valueString": "ISO" } ], "identifier": [ { "value": "05D2222542" } ], "type": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "XX" } ] } ], "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126" } ], "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Organization/1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039", "resource": { "resourceType": "Organization", "id": "1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "05D2222542" } ], "name": "ISO" } }, { "fullUrl": "Observation/1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd", "resource": { "resourceType": "Observation", "id": "1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id", "valueReference": { "reference": "Organization/1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470" } } ], "identifier": [ { "system": "urn:id:extID", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "95418-0" } ], "text": "Whether patient is employed in a healthcare setting" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectiveDateTime": "2021-08-02T00:00:00-05:00", "issued": "2021-08-02T00:00:00-05:00", "performer": [ { "reference": "Organization/1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8" } ], "valueCodeableConcept": { "coding": [ { "system": "HL70136", "code": "N" } ], "text": "No" } } }, { "fullUrl": "Organization/1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8", "resource": { "resourceType": "Organization", "id": "1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:2.16.840.1.113883.19.4.6" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", "valueString": "ISO" } ], "identifier": [ { "value": "05D2222542" } ], "type": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "XX" } ] } ], "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126-5285" } ], "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Organization/1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470", "resource": { "resourceType": "Organization", "id": "1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "05D2222542" } ] } }, { "fullUrl": "Observation/1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320", "resource": { "resourceType": "Observation", "id": "1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id", "valueReference": { "reference": "Organization/1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820" } } ], "identifier": [ { "system": "urn:id:extID", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "95417-2" } ], "text": "First test for condition of interest" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectiveDateTime": "2021-08-02T00:00:00-05:00", "issued": "2021-08-02T00:00:00-05:00", "performer": [ { "reference": "Organization/1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf" } ], "valueCodeableConcept": { "coding": [ { "system": "HL70136", "code": "N" } ], "text": "No" } } }, { "fullUrl": "Organization/1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf", "resource": { "resourceType": "Organization", "id": "1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:2.16.840.1.113883.19.4.6" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", "valueString": "ISO" } ], "identifier": [ { "value": "05D2222542" } ], "type": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "XX" } ] } ], "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126-5285" } ], "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Organization/1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820", "resource": { "resourceType": "Organization", "id": "1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "05D2222542" } ] } }, { "fullUrl": "Observation/1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7", "resource": { "resourceType": "Observation", "id": "1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id", "valueReference": { "reference": "Organization/1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4" } } ], "identifier": [ { "system": "urn:id:extID", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "95421-4" } ], "text": "Resides in a congregate care setting" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectiveDateTime": "2021-08-02T00:00:00-05:00", "issued": "2021-08-02T00:00:00-05:00", "performer": [ { "reference": "Organization/1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55" } ], "valueCodeableConcept": { "coding": [ { "system": "HL70136", "code": "Y" } ], "text": "Yes" } } }, { "fullUrl": "Organization/1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55", "resource": { "resourceType": "Organization", "id": "1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:2.16.840.1.113883.19.4.6" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", "valueString": "ISO" } ], "identifier": [ { "value": "05D2222542" } ], "type": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "XX" } ] } ], "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126-5285" } ], "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Organization/1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4", "resource": { "resourceType": "Organization", "id": "1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "05D2222542" } ] } }, { "fullUrl": "Observation/1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3", "resource": { "resourceType": "Observation", "id": "1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id", "valueReference": { "reference": "Organization/1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44" } } ], "identifier": [ { "system": "urn:id:extID", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory", "display": "Laboratory" } ] } ], "code": { "coding": [ { "system": "http://loinc.org", "code": "95419-8" } ], "text": "Has symptoms related to condition of interest" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectiveDateTime": "2021-08-02T00:00:00-05:00", "issued": "2021-08-02T00:00:00-05:00", "performer": [ { "reference": "Organization/1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004" } ], "valueCodeableConcept": { "coding": [ { "system": "HL70136", "code": "N" } ], "text": "No" } } }, { "fullUrl": "Organization/1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004", "resource": { "resourceType": "Organization", "id": "1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:2.16.840.1.113883.19.4.6" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", "valueString": "ISO" } ], "identifier": [ { "value": "05D2222542" } ], "type": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "XX" } ] } ], "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126-5285" } ], "contact": [ { "purpose": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/contactentity-type", "code": "ADMIN", "display": "Administrative" } ], "text": "Organization Medical Director" } } ] } }, { "fullUrl": "Organization/1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44", "resource": { "resourceType": "Organization", "id": "1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "value": "05D2222542" } ] } }, { "fullUrl": "Specimen/1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c", "resource": { "resourceType": "Specimen", "id": "1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "FGN" } ] }, "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "type": { "coding": [ { "system": "http://snomed.info/sct", "code": "445297001" } ], "text": "Swab of internal nose" }, "receivedTime": "2021-08-02T00:00:06-05:00", "collection": { "collectedDateTime": "2021-08-02T00:00:00-05:00", "bodySite": { "coding": [ { "system": "http://snomed.info/sct", "code": "53342003" } ], "text": "Internal nose structure (body structure)" } } } }, { "fullUrl": "DiagnosticReport/1667861768056908000.18c9371a-bbeb-40ff-9860-9aa68c5d2e00", "resource": { "resourceType": "DiagnosticReport", "id": "1667861768056908000.18c9371a-bbeb-40ff-9860-9aa68c5d2e00", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/result-status", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0123", "code": "F" } ] } } ], "identifier": [ { "system": "urn:id:extID", "value": "20210803131511.0147+0000" }, { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "FILL", "display": "Filler Identifier" } ] }, "system": "urn:id:Winchester_House", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "PLAC", "display": "Placer Identifier" } ] }, "system": "urn:id:Winchester_House", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "basedOn": [ { "reference": "ServiceRequest/1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0" } ], "status": "final", "code": { "coding": [ { "system": "http://loinc.org", "code": "94558-4" } ], "text": "SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "effectivePeriod": { "start": "2021-08-02T00:00:00-05:00", "end": "2021-08-02T00:00:00-05:00" }, "issued": "2021-08-02T00:00:00-05:00", "specimen": [ { "reference": "Specimen/1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c" } ], "result": [ { "reference": "Observation/1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f" }, { "reference": "Observation/1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd" }, { "reference": "Observation/1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320" }, { "reference": "Observation/1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7" }, { "reference": "Observation/1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3" } ] } }, { "fullUrl": "Practitioner/1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267", "resource": { "resourceType": "Practitioner", "id": "1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "identifier": [ {} ] } }, { "fullUrl": "Practitioner/1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75", "resource": { "resourceType": "Practitioner", "id": "1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/identifier-type", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "NPI" } ] } } ], "identifier": [ { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-namespace-id", "valueString": "CMS" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-universal-id", "valueOid": "urn:oid:2.16.840.1.113883.3.249" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "NPI", "display": "National provider identifier" } ] }, "system": "urn:id:CMS", "value": "1679892871" } ], "name": [ { "text": "Doctor Doolittle", "family": "Doolittle", "given": [ "Doctor" ] } ], "telecom": [ { "system": "phone", "value": "+1 123 456 7890", "use": "work" } ], "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "state": "CA", "postalCode": "95126" } ] } }, { "fullUrl": "Organization/1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f", "resource": { "resourceType": "Organization", "id": "1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "name": "Winchester House", "address": [ { "line": [ "6789 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95126" } ], "contact": [ { "telecom": [ { "system": "phone", "value": "+1 123 456 7890", "use": "work" } ] } ] } }, { "fullUrl": "PractitionerRole/1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df", "resource": { "resourceType": "PractitionerRole", "id": "1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "practitioner": { "reference": "Practitioner/1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75" }, "organization": { "reference": "Organization/1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f" } } }, { "fullUrl": "ServiceRequest/1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0", "resource": { "resourceType": "ServiceRequest", "id": "1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/collector-identifier", "valueReference": { "reference": "Practitioner/1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267" } }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/order-control", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0119", "code": "RE" } ] } }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/result-status", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0123", "code": "F" } ] } } ], "identifier": [ { "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "VN", "display": "Visit number" } ] }, "value": "20210803131511.0147+0000" }, { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "PLAC", "display": "Placer Identifier" } ] }, "system": "urn:id:Winchester_House", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "FILL", "display": "Filler Identifier" } ] }, "system": "urn:id:Winchester_House", "value": "1234d1d1-95fe-462c-8ac6-46728dba581c" } ], "status": "unknown", "intent": "order", "code": { "coding": [ { "system": "http://loinc.org", "code": "94558-4" } ], "text": "SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay" }, "subject": { "reference": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44" }, "occurrenceDateTime": "2021-08-02", "requester": { "reference": "PractitionerRole/1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df" } } }, { "fullUrl": "Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44", "resource": { "resourceType": "Patient", "id": "1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44", "meta": { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id", "valueCodeableConcept": { "coding": [ { "system": "https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html", "code": "P" } ] } }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-record-id", "valueId": "1234d1d1-95fe-462c-8ac6-46728dba581c" }, { "url": "http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version", "valueString": "2.5.1" } ] }, "extension": [ { "url": "http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v3-Race", "code": "2106-3" } ], "text": "White" } }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/ethnic-group", "valueCodeableConcept": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0189", "code": "N" } ], "text": "Non Hispanic or Latino" } }, { "url": "http://hl7.org/fhir/StructureDefinition/patient-animal", "valueCodeableConcept": {} } ], "identifier": [ { "extension": [ { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-facility-universal-id", "valueOid": "urn:oid:05D2222542" }, { "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-universal-id", "valueOid": "urn:oid:05D2222542" } ], "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "PI", "display": "Patient internal identifier" } ] }, "system": "urn:id:Winchester_House", "value": "09d12345-0987-1234-1234-111b1ee0879f" } ], "name": [ { "use": "official", "text": "Bugs C Bunny", "family": "Bunny", "given": [ "Bugs", "C" ] } ], "telecom": [ { "system": "phone", "value": "+1 123 456 7890", "use": "home" } ], "gender": "male", "birthDate": "1900-01-01", "deceasedBoolean": false, "address": [ { "line": [ "12345 Main St" ], "city": "San Jose", "district": "06085", "state": "CA", "postalCode": "95125", "country": "USA" } ] } } ] }
\ No newline at end of file
+{"resourceType":"Bundle","id":"1667861767830636000.7db38d22-b713-49fc-abfa-2edba9c12347","meta":{"lastUpdated":"2022-11-07T22:56:07.832+00:00"},"identifier":{"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},"type":"message","timestamp":"2021-08-03T13:15:11.015+00:00","entry":[{"fullUrl":"MessageHeader/c03f1b6b-cfc3-3477-89c0-d38316cd1a38","resource":{"resourceType":"MessageHeader","id":"c03f1b6b-cfc3-3477-89c0-d38316cd1a38","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"eventCoding":{"system":"http://terminology.hl7.org/CodeSystem/v2-0003","code":"R01","display":"ORU/ACK - Unsolicited transmission of an observation message"},"destination":[{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/facility-identifier","valueOid":"urn:oid:2.16.840.1.114222.4.1.214104"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/application-identifier","valueOid":"urn:oid:2.16.840.1.114222.4.3.3.10.1.1"}],"name":"CDPH CA REDIE","endpoint":"CDPH_CID"}],"sender":{"reference":"Organization/1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64"},"source":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-vendor-org","valueReference":{"reference":"Organization/1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-install-date","valueDateTime":"2021-07-26"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-binary-id","valueString":"0.1-SNAPSHOT"}],"name":"CDC PRIME - Atlanta,","software":"PRIME Data Hub","version":"0.1-SNAPSHOT","endpoint":"urn:oid:2.16.840.1.114222.4.1.237821"}}},{"fullUrl":"Organization/1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64","resource":{"resourceType":"Organization","id":"1667861767851428000.4998bfc4-e2a0-4174-ab9a-ec1889429a64","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"urn:oid:05D2222542"}],"name":"Winchester House","address":[{"country":"USA"}]}},{"fullUrl":"Organization/1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2","resource":{"resourceType":"Organization","id":"1667861767863091000.87c27799-355a-422e-9338-e9b936a761c2","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"name":"Centers for Disease Control and Prevention"}},{"fullUrl":"Provenance/1667861767909887000.56c99862-7264-4c8f-9d5e-896ac70a0d75","resource":{"resourceType":"Provenance","id":"1667861767909887000.56c99862-7264-4c8f-9d5e-896ac70a0d75","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"recorded":"2021-08-03T13:15:11.0147Z","activity":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0003","code":"R01","display":"ORU_R01"}]},"agent":[{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/provenance-participant-type","code":"author"}]},"who":{"reference":"Organization/1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1"}}],"entity":[{"role":"source","what":{"reference":"Device/1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea"}}]}},{"fullUrl":"Organization/1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1","resource":{"resourceType":"Organization","id":"1667861767907057000.c3030f30-9467-4e1d-95c7-30cce09dd5f1","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"name":"Winchester House","contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Device/1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea","resource":{"resourceType":"Device","id":"1667861767911610000.bd8aa647-0013-41c3-9d51-e05fa01876ea","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"type":{"coding":[{"system":"http://terminology.hl7.org/NamingSystem/uri","code":"2.16.840.1.114222.4.1.237821","display":"CDC PRIME - Atlanta,"}]}}],"deviceName":[{"name":"CDC PRIME - Atlanta,","type":"user-friendly-name"}]}},{"fullUrl":"Observation/1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f","resource":{"resourceType":"Observation","id":"1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id","valueReference":{"reference":"Organization/1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039"}}],"identifier":[{"system":"urn:id:extID","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"94558-5"}],"text":"SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectiveDateTime":"2021-08-02T00:00:00-05:00","issued":"2021-08-02T00:00:00-05:00","performer":[{"reference":"Organization/1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260415000"}],"text":"Not detected"},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0078","code":"N","display":"Normal"}]}],"method":{"coding":[{"system":"99ELR","code":"10811877011290_DIT"}],"text":"10811877011290"}}},{"fullUrl":"Organization/1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027","resource":{"resourceType":"Organization","id":"1667861767948602000.25043715-a246-4abb-8f4b-7277ebc17027","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:2.16.840.1.113883.19.4.6"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id","valueString":"ISO"}],"identifier":[{"value":"05D2222542"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"XX"}]}],"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126"}],"contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Organization/1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039","resource":{"resourceType":"Organization","id":"1667861767955540000.c1a6e3bd-7a1f-4cd3-8ec2-9db7f4ce6039","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"05D2222542"}],"name":"ISO"}},{"fullUrl":"Observation/1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd","resource":{"resourceType":"Observation","id":"1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id","valueReference":{"reference":"Organization/1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470"}}],"identifier":[{"system":"urn:id:extID","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"95418-0"}],"text":"Whether patient is employed in a healthcare setting"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectiveDateTime":"2021-08-02T00:00:00-05:00","issued":"2021-08-02T00:00:00-05:00","performer":[{"reference":"Organization/1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8"}],"valueCodeableConcept":{"coding":[{"system":"HL70136","code":"N"}],"text":"No"}}},{"fullUrl":"Organization/1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8","resource":{"resourceType":"Organization","id":"1667861767970948000.74f6a304-5bbc-40b0-8f3f-a423d1bc3fb8","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:2.16.840.1.113883.19.4.6"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id","valueString":"ISO"}],"identifier":[{"value":"05D2222542"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"XX"}]}],"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126-5285"}],"contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Organization/1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470","resource":{"resourceType":"Organization","id":"1667861767978089000.af51483e-b1e1-4523-b6ad-3c1d5742d470","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"05D2222542"}]}},{"fullUrl":"Observation/1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320","resource":{"resourceType":"Observation","id":"1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id","valueReference":{"reference":"Organization/1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820"}}],"identifier":[{"system":"urn:id:extID","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"95417-2"}],"text":"First test for condition of interest"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectiveDateTime":"2021-08-02T00:00:00-05:00","issued":"2021-08-02T00:00:00-05:00","performer":[{"reference":"Organization/1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf"}],"valueCodeableConcept":{"coding":[{"system":"HL70136","code":"N"}],"text":"No"}}},{"fullUrl":"Organization/1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf","resource":{"resourceType":"Organization","id":"1667861767990271000.886996f6-051c-43d8-9948-1d36422f8aaf","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:2.16.840.1.113883.19.4.6"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id","valueString":"ISO"}],"identifier":[{"value":"05D2222542"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"XX"}]}],"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126-5285"}],"contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Organization/1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820","resource":{"resourceType":"Organization","id":"1667861767998101000.72c561bc-39f3-4556-a275-5eca79c9c820","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"05D2222542"}]}},{"fullUrl":"Observation/1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7","resource":{"resourceType":"Observation","id":"1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id","valueReference":{"reference":"Organization/1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4"}}],"identifier":[{"system":"urn:id:extID","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"95421-4"}],"text":"Resides in a congregate care setting"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectiveDateTime":"2021-08-02T00:00:00-05:00","issued":"2021-08-02T00:00:00-05:00","performer":[{"reference":"Organization/1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55"}],"valueCodeableConcept":{"coding":[{"system":"HL70136","code":"Y"}],"text":"Yes"}}},{"fullUrl":"Organization/1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55","resource":{"resourceType":"Organization","id":"1667861768011635000.50bfb375-e966-457d-a925-b890d4647c55","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:2.16.840.1.113883.19.4.6"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id","valueString":"ISO"}],"identifier":[{"value":"05D2222542"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"XX"}]}],"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126-5285"}],"contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Organization/1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4","resource":{"resourceType":"Organization","id":"1667861768018078000.caf4414b-d653-46c7-8e07-8ed2450874b4","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"05D2222542"}]}},{"fullUrl":"Observation/1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3","resource":{"resourceType":"Observation","id":"1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/producer-id","valueReference":{"reference":"Organization/1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44"}}],"identifier":[{"system":"urn:id:extID","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"final","category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"code":{"coding":[{"system":"http://loinc.org","code":"95419-8"}],"text":"Has symptoms related to condition of interest"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectiveDateTime":"2021-08-02T00:00:00-05:00","issued":"2021-08-02T00:00:00-05:00","performer":[{"reference":"Organization/1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004"}],"valueCodeableConcept":{"coding":[{"system":"HL70136","code":"N"}],"text":"No"}}},{"fullUrl":"Organization/1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004","resource":{"resourceType":"Organization","id":"1667861768027523000.4ddd1587-c0f5-43c2-b5a1-57d2557d8004","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:2.16.840.1.113883.19.4.6"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id","valueString":"ISO"}],"identifier":[{"value":"05D2222542"}],"type":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"XX"}]}],"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126-5285"}],"contact":[{"purpose":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/contactentity-type","code":"ADMIN","display":"Administrative"}],"text":"Organization Medical Director"}}]}},{"fullUrl":"Organization/1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44","resource":{"resourceType":"Organization","id":"1667861768034563000.9add9712-e16f-4cfb-ba45-5f707e5e8e44","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"value":"05D2222542"}]}},{"fullUrl":"Specimen/1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c","resource":{"resourceType":"Specimen","id":"1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"identifier":[{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"FGN"}]},"value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"type":{"coding":[{"system":"http://snomed.info/sct","code":"445297001"}],"text":"Swab of internal nose"},"receivedTime":"2021-08-02T00:00:06-05:00","collection":{"collectedDateTime":"2021-08-02T00:00:00-05:00","bodySite":{"coding":[{"system":"http://snomed.info/sct","code":"53342003"}],"text":"Internal nose structure (body structure)"}}}},{"fullUrl":"DiagnosticReport/1667861768056908000.18c9371a-bbeb-40ff-9860-9aa68c5d2e00","resource":{"resourceType":"DiagnosticReport","id":"1667861768056908000.18c9371a-bbeb-40ff-9860-9aa68c5d2e00","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/result-status","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0123","code":"F"}]}}],"identifier":[{"system":"urn:id:extID","value":"20210803131511.0147+0000"},{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"FILL","display":"Filler Identifier"}]},"system":"urn:id:Winchester_House","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PLAC","display":"Placer Identifier"}]},"system":"urn:id:Winchester_House","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"basedOn":[{"reference":"ServiceRequest/1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0"}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"94558-4"}],"text":"SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"effectivePeriod":{"start":"2021-08-02T00:00:00-05:00","end":"2021-08-02T00:00:00-05:00"},"issued":"2021-08-02T00:00:00-05:00","specimen":[{"reference":"Specimen/1667861768049080000.f2ae4be6-3cf0-4615-b994-aea8a34fb21c"}],"result":[{"reference":"Observation/1667861767955966000.f3f94c27-e225-4aac-b6f5-2750f45dac4f"},{"reference":"Observation/1667861767978508000.c6cb1063-2f87-45d5-b8f9-798ac822ecdd"},{"reference":"Observation/1667861767998499000.d390c176-f9c4-4cea-892b-24b787ebf320"},{"reference":"Observation/1667861768018399000.6d1d1647-1c2f-4c2d-baf8-61c7d98fd6f7"},{"reference":"Observation/1667861768034907000.7d0b81c8-41d5-4738-9f77-1f8181d105a3"}]}},{"fullUrl":"Practitioner/1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267","resource":{"resourceType":"Practitioner","id":"1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]}}},{"fullUrl":"Practitioner/1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75","resource":{"resourceType":"Practitioner","id":"1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/identifier-type","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"NPI"}]}}],"identifier":[{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-namespace-id","valueString":"CMS"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-universal-id","valueOid":"urn:oid:2.16.840.1.113883.3.249"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"NPI","display":"National provider identifier"}]},"system":"urn:id:CMS","value":"1679892871"}],"name":[{"text":"Doctor Doolittle","family":"Doolittle","given":["Doctor"]}],"telecom":[{"system":"phone","value":"+1 123 456 7890","use":"work"}],"address":[{"line":["6789 Main St"],"city":"San Jose","state":"CA","postalCode":"95126"}]}},{"fullUrl":"Organization/1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f","resource":{"resourceType":"Organization","id":"1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"name":"Winchester House","address":[{"line":["6789 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95126"}],"contact":[{"telecom":[{"system":"phone","value":"+1 123 456 7890","use":"work"}]}]}},{"fullUrl":"PractitionerRole/1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df","resource":{"resourceType":"PractitionerRole","id":"1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"practitioner":{"reference":"Practitioner/1667861768077534000.4725986c-c3d8-4d0f-a310-270827ec2c75"},"organization":{"reference":"Organization/1667861768093053000.243d7442-744a-4a30-8ceb-7016738b869f"}}},{"fullUrl":"ServiceRequest/1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0","resource":{"resourceType":"ServiceRequest","id":"1667861768068290000.c4b44f97-f84a-49b2-9871-f5b3434d4ff0","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/collector-identifier","valueReference":{"reference":"Practitioner/1667861768065762000.258439c1-2c12-4e30-ab0e-2f8d6af18267"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/order-control","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0119","code":"RE"}]}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/result-status","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0123","code":"F"}]}}],"identifier":[{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"VN","display":"Visit number"}]},"value":"20210803131511.0147+0000"},{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PLAC","display":"Placer Identifier"}]},"system":"urn:id:Winchester_House","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"FILL","display":"Filler Identifier"}]},"system":"urn:id:Winchester_House","value":"1234d1d1-95fe-462c-8ac6-46728dba581c"}],"status":"unknown","intent":"order","code":{"coding":[{"system":"http://loinc.org","code":"94558-4"}],"text":"SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay"},"subject":{"reference":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44"},"occurrenceDateTime":"2021-08-02","requester":{"reference":"PractitionerRole/1667861768093461000.1af41fee-8c9a-4711-901b-3b363e4a12df"}}},{"fullUrl":"Patient/1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44","resource":{"resourceType":"Patient","id":"1667861767929699000.7dbebb1e-6497-4383-8c2f-de00717cbb44","meta":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/source-processing-id","valueCodeableConcept":{"coding":[{"system":"https://terminology.hl7.org/3.1.0/CodeSystem-v2-0103.html","code":"P"}]}},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-record-id","valueId":"1234d1d1-95fe-462c-8ac6-46728dba581c"},{"url":"http://ibm.com/fhir/cdm/StructureDefinition/source-data-model-version","valueString":"2.5.1"}]},"extension":[{"url":"http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-Race","code":"2106-3"}],"text":"White"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/ethnic-group","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0189","code":"N"}],"text":"Non Hispanic or Latino"}}],"identifier":[{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-facility-universal-id","valueOid":"urn:oid:05D2222542"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority-universal-id","valueOid":"urn:oid:05D2222542"}],"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PI","display":"Patient internal identifier"}]},"system":"urn:id:Winchester_House","value":"09d12345-0987-1234-1234-111b1ee0879f"}],"name":[{"use":"official","text":"Bugs C Bunny","family":"Bunny","given":["Bugs","C"]}],"telecom":[{"system":"phone","value":"+1 123 456 7890","use":"home"}],"gender":"male","birthDate":"1900-01-01","deceasedBoolean":false,"address":[{"line":["12345 Main St"],"city":"San Jose","district":"06085","state":"CA","postalCode":"95125","country":"USA"}]}}]}
\ No newline at end of file
diff --git a/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.fhir b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.fhir
new file mode 100644
index 00000000000..3ea30abb907
--- /dev/null
+++ b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.fhir
@@ -0,0 +1 @@
+{"resourceType":"Bundle","identifier":{"value":"0bab3f94-feb4-4915-939d-1adc5da201f3"},"type":"message","timestamp":"2024-06-05T18:55:01.277Z","entry":[{"fullUrl":"MessageHeader/5cbae40a-675e-49d4-b12a-04271de671a6","resource":{"resourceType":"MessageHeader","id":"5cbae40a-675e-49d4-b12a-04271de671a6","meta":{"tag":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0103","code":"P","display":"Production"}]},"eventCoding":{"system":"http://terminology.hl7.org/CodeSystem/v2-0003","code":"R01","display":"ORU/ACK - Unsolicited transmission of an observation message"},"destination":[{"name":"PRIME ReportStream","endpoint":"https://prime.cdc.gov/api/reports?option=SkipInvalidItems"}],"sender":{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"},"source":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-binary-id","valueString":"d5de310"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-install-date","valueInstant":"2024-06-05T15:42:52Z"},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/software-vendor-org","valueReference":{"reference":"Organization/07640c5d-87cd-488b-9343-a226c5166539"}}],"software":"PRIME SimpleReport","version":"d5de310","endpoint":"https://simplereport.gov"},"focus":[{"reference":"Provenance/d0dd19cd-76a0-4e16-ab1d-dc5838247b61"},{"reference":"DiagnosticReport/0bab3f94-feb4-4915-939d-1adc5da201f3"}]}},{"fullUrl":"Provenance/d0dd19cd-76a0-4e16-ab1d-dc5838247b61","resource":{"resourceType":"Provenance","id":"d0dd19cd-76a0-4e16-ab1d-dc5838247b61","recorded":"2024-06-05T18:55:01.277Z","activity":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0003","code":"R01","display":"ORU/ACK - Unsolicited transmission of an observation message"}]},"agent":[{"who":{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"}}]}},{"fullUrl":"DiagnosticReport/0bab3f94-feb4-4915-939d-1adc5da201f3","resource":{"resourceType":"DiagnosticReport","id":"0bab3f94-feb4-4915-939d-1adc5da201f3","identifier":[{"value":"0bab3f94-feb4-4915-939d-1adc5da201f3"}],"basedOn":[{"reference":"ServiceRequest/185170f3-4361-48ff-85e1-808a66624470"}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"94531-1"}]},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"effectiveDateTime":"2024-06-05T18:39:58+00:00","issued":"2024-06-05T18:55:01+00:00","specimen":[{"reference":"Specimen/dc7af370-fc07-4b00-abc7-9b5dd87cf4d2"}],"result":[{"reference":"Observation/5ab37a34-59f5-421f-92bd-baffaf26bb72"}]}},{"fullUrl":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67","resource":{"resourceType":"Patient","id":"7c0d1de9-270e-4d9c-a4ec-af92560cec67","extension":[{"url":"http://ibm.com/fhir/cdm/StructureDefinition/local-race-cd","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-Race","code":"2028-9"}],"text":"asian"}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/ethnic-group","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0189","code":"N","display":"Not Hispanic or Latino"}],"text":"Not Hispanic or Latino"}}],"identifier":[{"value":"7c0d1de9-270e-4d9c-a4ec-af92560cec67"}],"name":[{"family":"Granger","given":["Hermione"]}],"telecom":[{"system":"phone","value":"(212) 555 1234","use":"mobile"}],"gender":"female","birthDate":"1990-07-21","address":[{"line":["53 Buttonwood Ave"],"city":"Brooklyn","state":"NM","postalCode":"11224","country":"USA"}],"managingOrganization":{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"}}},{"fullUrl":"Organization/719ec8ad-cf59-405a-9832-c4065945c130","resource":{"resourceType":"Organization","id":"719ec8ad-cf59-405a-9832-c4065945c130","identifier":[{"use":"official","type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0301","code":"CLIA"}]},"value":"12D4567890"}],"name":"Testing Lab","telecom":[{"system":"phone","value":"(530) 867 5309","use":"work"}],"address":[{"line":["123 Beach Way"],"city":"Denver","state":"NM","postalCode":"80210","country":"USA"}]}},{"fullUrl":"Practitioner/ee29ccf5-631d-4b35-a6d4-30a61c0eb8d9","resource":{"resourceType":"Practitioner","id":"ee29ccf5-631d-4b35-a6d4-30a61c0eb8d9","identifier":[{"system":"http://hl7.org/fhir/sid/us-npi","value":"1245319599"}],"name":[{"family":"McTester","given":["Phil"]}],"telecom":[{"system":"phone","value":"(530) 867 5309","use":"work"}],"address":[{"line":["321 Ocean Drive"],"city":"Denver","state":"NM","postalCode":"80210","country":"USA"}]}},{"fullUrl":"Specimen/dc7af370-fc07-4b00-abc7-9b5dd87cf4d2","resource":{"resourceType":"Specimen","id":"dc7af370-fc07-4b00-abc7-9b5dd87cf4d2","identifier":[{"value":"80ed36a0-4bd1-42c3-bb56-81ea4ac1e75a"}],"type":{"coding":[{"system":"http://snomed.info/sct","code":"258500001"}],"text":"Nasopharyngeal swab"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"receivedTime":"2024-06-05T18:39:58+00:00","collection":{"collectedDateTime":"2024-06-05T18:39:58+00:00","bodySite":{"coding":[{"system":"http://snomed.info/sct","code":"87100004"}],"text":"Topography unknown (body structure)"}}}},{"fullUrl":"ServiceRequest/185170f3-4361-48ff-85e1-808a66624470","resource":{"resourceType":"ServiceRequest","id":"185170f3-4361-48ff-85e1-808a66624470","extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/order-control","valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0119","code":"RE"}]}},{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/order-effective-date","valueDateTime":"2024-06-05T18:54:58+00:00"}],"status":"completed","intent":"order","code":{"coding":[{"system":"http://loinc.org","code":"94531-1"}]},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"requester":{"reference":"PractitionerRole/57a1a000-16e5-461a-930a-2e4779944bc2"},"performer":[{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"}],"supportingInfo":[{"reference":"Observation/dd5369b2-91e7-32d9-8c7f-884cad6b0391"},{"reference":"Observation/fdf748f4-b187-31c6-b08d-334afc1c6a49"},{"reference":"Observation/4c669397-3b8b-3448-a7c8-d2fb8c5afde5"},{"reference":"Observation/4456d162-bfd8-37f3-aafe-1e6444c53934"},{"reference":"Observation/0042a1cb-8473-3181-9b65-fcb08dc112a3"}]}},{"fullUrl":"Device/d303372c-70cb-46b7-bf74-23f4dc91e661","resource":{"resourceType":"Device","id":"d303372c-70cb-46b7-bf74-23f4dc91e661","identifier":[{"type":{"coding":[{"code":"MNI"}]}}],"manufacturer":"Access Bio, Inc.","deviceName":[{"name":"CareStart COVID-19 MDx RT-PCR","type":"model-name"}]}},{"fullUrl":"PractitionerRole/57a1a000-16e5-461a-930a-2e4779944bc2","resource":{"resourceType":"PractitionerRole","id":"57a1a000-16e5-461a-930a-2e4779944bc2","practitioner":{"reference":"Practitioner/ee29ccf5-631d-4b35-a6d4-30a61c0eb8d9"},"organization":{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"}}},{"fullUrl":"Organization/07640c5d-87cd-488b-9343-a226c5166539","resource":{"resourceType":"Organization","id":"07640c5d-87cd-488b-9343-a226c5166539","name":"SimpleReport"}},{"fullUrl":"Observation/5ab37a34-59f5-421f-92bd-baffaf26bb72","resource":{"resourceType":"Observation","id":"5ab37a34-59f5-421f-92bd-baffaf26bb72","status":"final","code":{"coding":[{"system":"http://loinc.org","code":"94500-6"}],"text":"COVID-19"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"issued":"2024-06-05T18:54:58.594Z","performer":[{"reference":"Organization/719ec8ad-cf59-405a-9832-c4065945c130"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260373001","display":"Detected"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0078","code":"A","display":"Abnormal"}]}],"method":{"extension":[{"url":"https://reportstream.cdc.gov/fhir/StructureDefinition/testkit-name-id","valueCoding":{"code":"CareStart COVID-19 MDx RT-PCR_Access Bio, Inc."}}],"coding":[{"display":"CareStart COVID-19 MDx RT-PCR"}]},"specimen":{"reference":"Specimen/dc7af370-fc07-4b00-abc7-9b5dd87cf4d2"},"device":{"reference":"Device/d303372c-70cb-46b7-bf74-23f4dc91e661"}}},{"fullUrl":"Observation/dd5369b2-91e7-32d9-8c7f-884cad6b0391","resource":{"resourceType":"Observation","id":"dd5369b2-91e7-32d9-8c7f-884cad6b0391","identifier":[{"use":"official","type":{"coding":[{"system":"http://loinc.org","code":"81959-9","display":"Public health laboratory ask at order entry panel"}]}}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"95419-8","display":"Has symptoms related to condition of interest"}],"text":"Has symptoms related to condition of interest"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/ValueSet/v2-0136","code":"Y","display":"Yes"}]}}},{"fullUrl":"Observation/fdf748f4-b187-31c6-b08d-334afc1c6a49","resource":{"resourceType":"Observation","id":"fdf748f4-b187-31c6-b08d-334afc1c6a49","identifier":[{"use":"official","type":{"coding":[{"system":"http://loinc.org","code":"81959-9","display":"Public health laboratory ask at order entry panel"}]}}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"11368-8","display":"Illness or injury onset date and time"}],"text":"Illness or injury onset date and time"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"valueDateTime":"2024-06-01"}},{"fullUrl":"Observation/4c669397-3b8b-3448-a7c8-d2fb8c5afde5","resource":{"resourceType":"Observation","id":"4c669397-3b8b-3448-a7c8-d2fb8c5afde5","identifier":[{"use":"official","type":{"coding":[{"system":"http://loinc.org","code":"81959-9","display":"Public health laboratory ask at order entry panel"}]}}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"82810-3","display":"Pregnancy status"}],"text":"Pregnancy status"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"60001007","display":"Not pregnant"}]}}},{"fullUrl":"Observation/4456d162-bfd8-37f3-aafe-1e6444c53934","resource":{"resourceType":"Observation","id":"4456d162-bfd8-37f3-aafe-1e6444c53934","identifier":[{"use":"official","type":{"coding":[{"system":"http://loinc.org","code":"81959-9","display":"Public health laboratory ask at order entry panel"}]}}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"95418-0","display":"Employed in a healthcare setting"}],"text":"Employed in a healthcare setting"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/ValueSet/v2-0136","code":"Y","display":"Yes"}]}}},{"fullUrl":"Observation/0042a1cb-8473-3181-9b65-fcb08dc112a3","resource":{"resourceType":"Observation","id":"0042a1cb-8473-3181-9b65-fcb08dc112a3","identifier":[{"use":"official","type":{"coding":[{"system":"http://loinc.org","code":"81959-9","display":"Public health laboratory ask at order entry panel"}]}}],"status":"final","code":{"coding":[{"system":"http://loinc.org","code":"95421-4","display":"Resides in a congregate care setting"}],"text":"Resides in a congregate care setting"},"subject":{"reference":"Patient/7c0d1de9-270e-4d9c-a4ec-af92560cec67"},"valueCodeableConcept":{"coding":[{"system":"http://terminology.hl7.org/ValueSet/v2-0136","code":"Y","display":"Yes"}]}}}]}
\ No newline at end of file
diff --git a/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.hl7 b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.hl7
new file mode 100644
index 00000000000..686a09d9ab4
--- /dev/null
+++ b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_NM_20240702-0001.hl7
@@ -0,0 +1,12 @@
+MSH|^~\&|CDC PRIME - Atlanta, Georgia (Dekalb)^2.16.840.1.114222.4.1.237821^ISO|Testing Lab^12D4567890^CLIA|NMDOH^2.16.840.1.113883.3.5364^ISO|NMDOH^2.16.840.1.113883.3.5364^ISO|20240605185501+0000||ORU^R01^ORU_R01|0bab3f94-feb4-4915-939d-1adc5da201f3|P|2.5.1|||NE|NE|USA|UNICODE UTF-8|ENG^English^ISO||PHLabReport-NoAck^ELR_Receiver^2.16.840.1.113883.9.11^ISO
+SFT|Centers for Disease Control and Prevention|0.2-SNAPSHOT|PRIME ReportStream|0.2-SNAPSHOT||20240605154252+0000
+PID|1||7c0d1de9-270e-4d9c-a4ec-af92560cec67^^^Testing Lab&12D4567890&CLIA^PI^Testing Lab&12D4567890&CLIA||Granger^Hermione^^^^^L||19900721|F||2028-9^asian^HL70005^^^^2.5.1^^asian|53 Buttonwood Ave^^Brooklyn^NM^11224^USA||(212) 555 1234^PRS^CP^^1^212^5551234^^^^^(212) 555 1234|||||||||N^Not Hispanic or Latino^HL70189^^^^2.9^^Not Hispanic or Latino||||||||N
+ORC|RE|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|20240605185458+0000||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|321 Ocean Drive^^Denver^NM^80210^USA
+OBR|1|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|94531-1^SARS-CoV-2 (COVID-19) RNA panel - Respiratory system specimen by NAA with probe detection^LN|||20240605183958+0000|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|||||20240605185501+0000|||F
+OBX|1|CWE|94500-6^SARS-CoV-2 (COVID-19) RNA [Presence] in Respiratory system specimen by NAA with probe detection^LN^^^^^^COVID-19||260373001^Detected^SCT|||A^Abnormal^HL70078^^^^2.7|||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.^CareStart COVID-19 MDx RT-PCR^^^^^^^CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.|^^MNI|20240605185458+0000||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA
+OBX|2|CWE|95419-8^Has symptoms related to condition of interest^LN^^^^2.69^^Has symptoms related to condition of interest||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|||||QST
+OBX|3|DT|11368-8^Illness or injury onset date and time^LN^^^^^^Illness or injury onset date and time||20240601000000+0000||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|||||QST
+OBX|4|CWE|82810-3^Pregnancy status^LN^^^^2.68^^Pregnancy status||60001007^Not pregnant^SCT||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|||||QST
+OBX|5|CWE|95418-0^Employed in a healthcare setting^LN^^^^2.69^^Employed in a healthcare setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|||||QST
+OBX|6|CWE|95421-4^Resides in a congregate care setting^LN^^^^2.69^^Resides in a congregate care setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^NM^80210^USA|||||QST
+SPM|1|0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA^0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA||258500001^Nasopharyngeal swab^SCT^^^^2.67^^Nasopharyngeal swab||||87100004^Topography unknown (body structure)^SCT^^^^^^Topography unknown (body structure)|||||||||20240605183958+0000|20240605183958+0000
diff --git a/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_OK_20240628-0001.hl7 b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_OK_20240628-0001.hl7
index 8129ce22443..6389be02746 100644
--- a/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_OK_20240628-0001.hl7
+++ b/prime-router/src/testIntegration/resources/datatests/FHIR_to_HL7/sample_OK_20240628-0001.hl7
@@ -1 +1 @@
-MSH|^~\&|CDC PRIME - Atlanta^2.16.840.1.114222.4.1.237821^ISO|Testing Lab^12D4567890^CLIA|||20240605185501+0000||ORU^R01^ORU_R01|0bab3f94-feb4-4915-939d-1adc5da201f3|P|2.5.1|||NE|NE|USA|UNICODE UTF-8|ENG^English^ISO||PHLabReport-NoAck^ELR_Receiver^2.16.840.1.113883.9.11^ISO
SFT|Centers for Disease Control and Prevention|0.2-SNAPSHOT|PRIME ReportStream|0.2-SNAPSHOT||20240605154252+0000
PID|1||7c0d1de9-270e-4d9c-a4ec-af92560cec67^^^Testing Lab&12D4567890&CLIA^PI^Testing Lab&12D4567890&CLIA||Granger^Hermione^^^^^L||19900721|F||2028-9^asian^HL70005^^^^2.5.1^^asian|53 Buttonwood Ave^^Brooklyn^OK^11224^USA||(212) 555 1234^PRS^CP^^1^212^5551234^^^^^(212) 555 1234|||||||||N^Not Hispanic or Latino^HL70189^^^^2.9^^Not Hispanic or Latino||||||||N
ORC|RE|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|20240605185458+0000||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|321 Ocean Drive^^Denver^OK^80210^USA
OBR|1|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|94531-1^SARS-CoV-2 (COVID-19) RNA panel - Respiratory system specimen by NAA with probe detection^LN|||20240605183958+0000|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|||||20240605185501+0000|||F
OBX|1|CWE|94500-6^SARS-CoV-2 (COVID-19) RNA [Presence] in Respiratory system specimen by NAA with probe detection^LN^^^^^^COVID-19||260373001^Detected^SCT|||A^Abnormal^HL70078^^^^2.7|||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.^CareStart COVID-19 MDx RT-PCR^^^^^^^CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.|^^MNI|20240605185458+0000||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA
OBX|2|CWE|95419-8^Has symptoms related to condition of interest^LN^^^^2.69^^Has symptoms related to condition of interest||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|3|DT|11368-8^Illness or injury onset date and time^LN^^^^^^Illness or injury onset date and time||20240601000000+0000||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|4|CWE|82810-3^Pregnancy status^LN^^^^2.68^^Pregnancy status||60001007^Not pregnant^SCT||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|5|CWE|95418-0^Employed in a healthcare setting^LN^^^^2.69^^Employed in a healthcare setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|6|CWE|95421-4^Resides in a congregate care setting^LN^^^^2.69^^Resides in a congregate care setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
SPM|1|0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA^0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA||258500001^Nasopharyngeal swab^SCT^^^^2.67^^Nasopharyngeal swab||||87100004^Topography unknown (body structure)^SCT^^^^^^Topography unknown (body structure)|||||||||20240605183958+0000|20240605183958+0000
\ No newline at end of file
+MSH|^~\&|CDC PRIME - Atlanta^2.16.840.1.114222.4.1.237821^ISO|Testing Lab^12D4567890^CLIA|OSDHMessaging^2.16.840.1.113883.3.1014.4^ISO|OSDH^2.16.840.1.113883.3.1014^ISO|20240605185501+0000||ORU^R01^ORU_R01|0bab3f94-feb4-4915-939d-1adc5da201f3|P|2.5.1|||NE|NE|USA|UNICODE UTF-8|ENG^English^ISO||PHLabReport-NoAck^ELR_Receiver^2.16.840.1.113883.9.11^ISO
SFT|Centers for Disease Control and Prevention|0.2-SNAPSHOT|PRIME ReportStream|0.2-SNAPSHOT||20240605154252+0000
PID|1||7c0d1de9-270e-4d9c-a4ec-af92560cec67^^^Testing Lab&12D4567890&CLIA^PI^Testing Lab&12D4567890&CLIA||Granger^Hermione^^^^^L||19900721|F||2028-9^asian^HL70005^^^^2.5.1^^asian|53 Buttonwood Ave^^Brooklyn^OK^11224^USA||(212) 555 1234^PRS^CP^^1^212^5551234^^^^^(212) 555 1234|||||||||N^Not Hispanic or Latino^HL70189^^^^2.9^^Not Hispanic or Latino||||||||N
ORC|RE|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|20240605185458+0000||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|321 Ocean Drive^^Denver^OK^80210^USA
OBR|1|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|0bab3f94-feb4-4915-939d-1adc5da201f3^Testing Lab^12D4567890^CLIA|94531-1^SARS-CoV-2 (COVID-19) RNA panel - Respiratory system specimen by NAA with probe detection^LN|||20240605183958+0000|||||||||1245319599^McTester^Phil^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI|(530) 867 5309^WPN^PH^^1^530^8675309^^^^^(530) 867 5309|||||20240605185501+0000|||F
OBX|1|CWE|94500-6^SARS-CoV-2 (COVID-19) RNA [Presence] in Respiratory system specimen by NAA with probe detection^LN^^^^^^COVID-19||260373001^Detected^SCT|||A^Abnormal^HL70078^^^^2.7|||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.^CareStart COVID-19 MDx RT-PCR^^^^^^^CareStart COVID-19 MDx RT-PCR_Access Bio, Inc.|^^MNI|20240605185458+0000||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA
OBX|2|CWE|95419-8^Has symptoms related to condition of interest^LN^^^^2.69^^Has symptoms related to condition of interest||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|3|DT|11368-8^Illness or injury onset date and time^LN^^^^^^Illness or injury onset date and time||20240601000000+0000||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|4|CWE|82810-3^Pregnancy status^LN^^^^2.68^^Pregnancy status||60001007^Not pregnant^SCT||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|5|CWE|95418-0^Employed in a healthcare setting^LN^^^^2.69^^Employed in a healthcare setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
OBX|6|CWE|95421-4^Resides in a congregate care setting^LN^^^^2.69^^Resides in a congregate care setting||Y^Yes^HL70136||||||F|||20240605183958+0000|12D4567890^Testing Lab^CLIA||||||||Testing Lab^L^^^^CLIA&2.16.840.1.113883.4.7&ISO^XX^^^12D4567890|123 Beach Way^^Denver^OK^80210^USA|||||QST
SPM|1|0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA^0bab3f94-feb4-4915-939d-1adc5da201f3&Testing Lab&12D4567890&CLIA||258500001^Nasopharyngeal swab^SCT^^^^2.67^^Nasopharyngeal swab||||87100004^Topography unknown (body structure)^SCT^^^^^^Topography unknown (body structure)|||||||||20240605183958+0000|20240605183958+0000
\ No newline at end of file
diff --git a/prime-router/src/testIntegration/resources/datatests/translation-test-config.csv b/prime-router/src/testIntegration/resources/datatests/translation-test-config.csv
index bfb60ce4819..766557a8781 100644
--- a/prime-router/src/testIntegration/resources/datatests/translation-test-config.csv
+++ b/prime-router/src/testIntegration/resources/datatests/translation-test-config.csv
@@ -23,7 +23,7 @@ HL7_to_FHIR_to_HL7/hci.hl7,,HL7_to_FHIR_to_HL7/hci-expected.hl7,classpath:/metad
FHIR_to_HL7/sample_SR_1_20230302-0001.fhir,,FHIR_to_HL7/sample_SR_1_20230302-0001.hl7,,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml",ca-test.default-receiver,"",""
FHIR_to_HL7/sample_SR_1_20230302-0001-missing-data.fhir,,FHIR_to_HL7/sample_SR_1_20230302-0001-missing-data.hl7,,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml",ca-test.default-receiver,"",""
FHIR_to_HL7/sample_SR_2_20231024-0002.fhir,,FHIR_to_HL7/sample_SR_2_20231024-0002.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/NY/NY-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
-FHIR_to_HL7/sample_OML_20230831-0001.fhir,,FHIR_to_HL7/sample_OML_20230831-0001.hl7,classpath:/metadata/hl7_mapping/receivers/Flexion/TILabOrder.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/Flexion/TILabOrder.yml","","",""
+FHIR_to_HL7/sample_OML_20230831-0001.fhir,,FHIR_to_HL7/sample_OML_20230831-0001.hl7,classpath:/metadata/hl7_mapping/receivers/Flexion/etor-oml-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/Flexion/etor-sender-transform.yml","","",""
FHIR_to_HL7/sample_RI_20231213-0001.fhir,,FHIR_to_HL7/sample_RI_20231213-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/RI/RI-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
FHIR_to_HL7/simple_report_with_date_aoe_question.fhir,,FHIR_to_HL7/simple_report_with_date_aoe_question.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/CA/CA-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
HL7_to_FHIR/sample_orm_20230809-001.hl7,,HL7_to_FHIR/sample_orm_20230809-001-with-enrichment.fhir,,FHIR,PASS,"","","","","","classpath:/enrichments/testing2.yml"
@@ -50,6 +50,7 @@ FHIR_to_HL7/sample_PR_20240624-0001.fhir,,FHIR_to_HL7/sample_PR_20240624-0001.hl
FHIR_to_HL7/sample_OK_20240628-0001.fhir,,FHIR_to_HL7/sample_OK_20240628-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/OK/OK-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
FHIR_to_HL7/sample_MO_20240628-0001.fhir,,FHIR_to_HL7/sample_MO_20240628-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/MO/MO-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
FHIR_to_HL7/sample_TN_20240701-0001.fhir,,FHIR_to_HL7/sample_TN_20240701-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/TN/TN-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
-FHIR_to_HL7/sample_VT_20240628-0001.fhir,,FHIR_to_HL7/sample_VT_20240628-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/VT/VT-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","","classpath:/enrichments/vt-enrichment.yml"
+FHIR_to_HL7/sample_VT_20240628-0001.fhir,,FHIR_to_HL7/sample_VT_20240628-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/VT/VT-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","vt-doh.full-elr","",""
FHIR_to_HL7/sample_MH_20240701-0001.fhir,,FHIR_to_HL7/sample_MH_20240701-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/MH/MH-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
-FHIR_to_HL7/sample_PA_20240703-0001.fhir,,FHIR_to_HL7/sample_PA_20240703-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/PA/PA-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
\ No newline at end of file
+FHIR_to_HL7/sample_PA_20240703-0001.fhir,,FHIR_to_HL7/sample_PA_20240703-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/PA/PA-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
+FHIR_to_HL7/sample_NM_20240702-0001.fhir,,FHIR_to_HL7/sample_NM_20240702-0001.hl7,classpath:/metadata/hl7_mapping/receivers/STLTs/NM/NM-receiver-transform.yml,HL7,PASS,"","","classpath:/metadata/fhir_transforms/senders/SimpleReport/simple-report-sender-transform.yml","","",""
\ No newline at end of file
diff --git a/prime-router/src/testIntegration/resources/settings/organizations.yml b/prime-router/src/testIntegration/resources/settings/organizations.yml
index 042f65e051b..94a76b29998 100644
--- a/prime-router/src/testIntegration/resources/settings/organizations.yml
+++ b/prime-router/src/testIntegration/resources/settings/organizations.yml
@@ -472,4 +472,107 @@
credentialName: DEFAULT-SFTP
externalName: ""
timeZone: "PACIFIC"
+ dateTimeFormat: "OFFSET"
+- name: "vt-doh"
+ description: "Vermont Department of Health"
+ jurisdiction: "STATE"
+ stateCode: "VT"
+ countyName: null
+ filters: null
+ featureFlags: null
+ keys: null
+ senders: []
+ receivers:
+ - name: "full-elr"
+ organizationName: "vt-doh"
+ topic: "full-elr"
+ customerStatus: "active"
+ translation:
+ schemaName: "classpath:/metadata/hl7_mapping/receivers/STLTs/VT/VT-receiver-transform.yml"
+ useTestProcessingMode: false
+ useBatchHeaders: true
+ receivingApplicationName: "NBS^2.16.840.1.114222.4.1.185.1^ISO"
+ receivingApplicationOID: "2.16.840.1.114222.4.1.185.1"
+ receivingFacilityName: "VDH^2.16.840.1.114222.4.1.185^ISO"
+ receivingFacilityOID: "2.16.840.1.114222.4.1.185"
+ messageProfileId: null
+ replaceValue:
+ MSH-3-1: "CDC PRIME - Atlanta,"
+ replaceValueAwithB: null
+ reportingFacilityName: null
+ reportingFacilityId: null
+ reportingFacilityIdType: null
+ suppressQstForAoe: false
+ suppressHl7Fields: "ORC-4-2, ORC-4-3, ORC-4-4, OBX-18-1, OBX-18-2, OBX-18-3,\
+ \ OBX-18-4, OBX-15-3, PID-15-1, PID-15-2, PID-15-3, MSH-19-1, MSH-19-2, MSH-19-3"
+ suppressAoe: true
+ defaultAoeToUnknown: false
+ replaceUnicodeWithAscii: false
+ useBlankInsteadOfUnknown: null
+ truncateHDNamespaceIds: true
+ truncateHl7Fields: "OBX-23-1"
+ usePid14ForPatientEmail: true
+ convertTimestampToDateTime: null
+ cliaForOutOfStateTesting: null
+ cliaForSender: {}
+ phoneNumberFormatting: "STANDARD"
+ suppressNonNPI: true
+ processingModeCode: null
+ replaceDiiWithOid: null
+ applyOTCDefault: false
+ useOrderingFacilityName: "STANDARD"
+ valueSetOverrides: {}
+ nameFormat: "STANDARD"
+ receivingOrganization: null
+ convertPositiveDateTimeOffsetToNegative: false
+ stripInvalidCharsRegex: null
+ convertDateTimesToReceiverLocalTime: false
+ useHighPrecisionHeaderDateTimeFormat: false
+ type: "HL7"
+ truncationConfig:
+ truncateHDNamespaceIds: true
+ truncateHl7Fields:
+ - "OBX-23-1"
+ customLengthHl7Fields: {}
+ jurisdictionalFilter:
+ - "(Bundle.entry.resource.ofType(ServiceRequest)[0].requester.resolve().organization.resolve().address.state = 'VT') or (Bundle.entry.resource.ofType(Patient).address.state = 'VT')"
+ qualityFilter:
+ - "Bundle.identifier.value.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.family.exists()"
+ - "Bundle.entry.resource.ofType(Patient).name.given.count() > 0"
+ - "Bundle.entry.resource.ofType(Patient).birthDate.exists()"
+ - "Bundle.entry.resource.ofType(Specimen).where(type.empty()).count() = 0"
+ - "Bundle.entry.resource.ofType(Patient).where(address.line[0].empty() and address.postalCode.empty() and telecom.where(system = 'phone').value.empty() and telecom.where(system = 'email').value.empty()).count() = 0"
+ - "((Bundle.entry.resource.ofType(Specimen).collection.collectedPeriod.exists() or Bundle.entry.resource.ofType(Specimen).collection.collected.exists())
+ or (Bundle.entry.resource.ofType(ServiceRequest).occurrence.exists() or Bundle.entry.resource.ofType(Observation).effective.exists()))"
+ routingFilter:
+ - "Bundle.entry.resource.ofType(DiagnosticReport).where(status in 'final'|'corrected'|'preliminary'|'amended').exists()"
+ processingModeFilter: []
+ reverseTheQualityFilter: false
+ conditionFilter:
+ # Accept COVID only
+ - "(%resource.code.coding.extension('https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code').value.where(code in ('840539006')).exists())"
+ mappedConditionFilter: []
+ deidentify: false
+ deidentifiedValue: ""
+ timing:
+ operation: "MERGE"
+ numberPerDay: 12
+ initialTime: "01:15"
+ timeZone: "EASTERN"
+ maxReportCount: 100
+ whenEmpty:
+ action: "NONE"
+ onlyOncePerDay: false
+ description: "ELR Primary for VT DOH"
+ transport:
+ type: SFTP
+ host: sftp
+ port: 22
+ filePath: ./upload
+ credentialName: DEFAULT-SFTP
+ externalName: "VT ELR Primary"
+ enrichmentSchemaNames:
+ - "classpath:/metadata/fhir_transforms/common/suppress-non-npi.yml,classpath:/metadata/fhir_transforms/common/suppress-non-npi.yml"
+ timeZone: null
dateTimeFormat: "OFFSET"
\ No newline at end of file
diff --git a/shared/src/main/kotlin/gov/cdc/prime/reportstream/shared/SubmissionQueueMessage.kt b/shared/src/main/kotlin/gov/cdc/prime/reportstream/shared/SubmissionQueueMessage.kt
new file mode 100644
index 00000000000..8e2115597ef
--- /dev/null
+++ b/shared/src/main/kotlin/gov/cdc/prime/reportstream/shared/SubmissionQueueMessage.kt
@@ -0,0 +1,5 @@
+package gov.cdc.prime.reportstream.shared
+
+import java.util.UUID
+
+data class SubmissionQueueMessage(val reportId: UUID, val blobUrl: String, val headers: Map)
\ No newline at end of file
diff --git a/submissions/build.gradle.kts b/submissions/build.gradle.kts
index 4fd0c709fb7..427fc4faccf 100644
--- a/submissions/build.gradle.kts
+++ b/submissions/build.gradle.kts
@@ -15,16 +15,26 @@ extra["springCloudAzureVersion"] = "5.13.0"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.azure.spring:spring-cloud-azure-starter-storage")
+ implementation("com.microsoft.azure:applicationinsights-runtime-attach:3.5.3")
+ implementation("com.microsoft.azure:applicationinsights-web:2.6.4")
+ implementation("com.microsoft.azure:applicationinsights-logging-logback:2.6.4")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-testcontainers")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+ testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.8.1")
implementation(project(":shared"))
}
+// There is a conflict in logging implementations. Excluded these in favor of using log4j-slf4j2-impl
+configurations.all {
+ exclude(group = "org.apache.logging.log4j", module = "log4j-to-slf4j")
+ exclude(group = "ch.qos.logback")
+}
+
dependencyManagement {
imports {
mavenBom("com.azure.spring:spring-cloud-azure-dependencies:${property("springCloudAzureVersion")}")
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/CustomMediaTypes.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/CustomMediaTypes.kt
new file mode 100644
index 00000000000..232d19d42df
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/CustomMediaTypes.kt
@@ -0,0 +1,8 @@
+package gov.cdc.prime.reportstream.submissions
+
+import org.springframework.http.MediaType
+
+object CustomMediaTypes {
+ val APPLICATION_HL7_V2 = MediaType("application", "hl7-v2")
+ val APPLICATION_FHIR_NDJSON = MediaType("application", "fhir+ndjson")
+}
\ No newline at end of file
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/ReportReceivedEvent.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/ReportReceivedEvent.kt
new file mode 100644
index 00000000000..d68f8c5e05e
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/ReportReceivedEvent.kt
@@ -0,0 +1,16 @@
+package gov.cdc.prime.reportstream.submissions
+
+import java.time.Instant
+import java.util.UUID
+
+data class ReportReceivedEvent(
+ val timeStamp: Instant,
+ val reportId: UUID,
+ val parentReportId: UUID,
+ val rootReportId: UUID,
+ val headers: Map,
+ val sender: String,
+ val senderIP: String,
+ val fileSize: String,
+ val blobUrl: String,
+)
\ No newline at end of file
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionsApplication.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionsApplication.kt
index ab98cecd4ea..4dead92d92b 100644
--- a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionsApplication.kt
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/SubmissionsApplication.kt
@@ -1,5 +1,6 @@
package gov.cdc.prime.reportstream.submissions
+import com.microsoft.applicationinsights.attach.ApplicationInsights
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@@ -7,5 +8,6 @@ import org.springframework.boot.runApplication
class SubmissionsApplication
fun main(args: Array) {
+ ApplicationInsights.attach()
runApplication(*args)
}
\ No newline at end of file
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/TelemetryService.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/TelemetryService.kt
new file mode 100644
index 00000000000..92bab166a56
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/TelemetryService.kt
@@ -0,0 +1,30 @@
+package gov.cdc.prime.reportstream.submissions
+
+import com.microsoft.applicationinsights.TelemetryClient
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+
+@Service
+class TelemetryService {
+ private val logger = LoggerFactory.getLogger(TelemetryService::class.java)
+ private var telemetryClient: TelemetryClient? = null
+
+ init {
+ val connectionString = System.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING")
+
+ if (!connectionString.isNullOrBlank()) {
+ telemetryClient = TelemetryClient()
+ } else {
+ telemetryClient = null
+ logger.warn("Application Insights connection string is not set. Telemetry will not be sent.")
+ }
+ }
+
+ fun trackEvent(name: String, properties: Map = emptyMap()) {
+ telemetryClient?.trackEvent(name, properties, null)
+ }
+
+ fun flush() {
+ telemetryClient?.flush()
+ }
+}
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/WebConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/WebConfig.kt
new file mode 100644
index 00000000000..fbb18645325
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/WebConfig.kt
@@ -0,0 +1,25 @@
+package gov.cdc.prime.reportstream.submissions
+
+ import gov.cdc.prime.reportstream.submissions.CustomMediaTypes.APPLICATION_FHIR_NDJSON
+import gov.cdc.prime.reportstream.submissions.CustomMediaTypes.APPLICATION_HL7_V2
+import org.springframework.context.annotation.Configuration
+import org.springframework.http.MediaType
+import org.springframework.http.converter.HttpMessageConverter
+import org.springframework.http.converter.StringHttpMessageConverter
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
+import java.nio.charset.StandardCharsets
+
+ @Configuration
+ class WebConfig : WebMvcConfigurer {
+
+ override fun extendMessageConverters(converters: MutableList>) {
+ val stringConverter = StringHttpMessageConverter(StandardCharsets.UTF_8)
+ stringConverter.setSupportedMediaTypes(
+ listOf(
+ MediaType(APPLICATION_HL7_V2),
+ MediaType(APPLICATION_FHIR_NDJSON)
+ )
+ )
+ converters.add(stringConverter)
+ }
+ }
\ No newline at end of file
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AzureConfig.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AzureConfig.kt
new file mode 100644
index 00000000000..73205780942
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/config/AzureConfig.kt
@@ -0,0 +1,56 @@
+package gov.cdc.prime.reportstream.submissions.config
+
+import com.azure.data.tables.TableClient
+import com.azure.data.tables.TableServiceClientBuilder
+import com.azure.storage.blob.BlobContainerClient
+import com.azure.storage.blob.BlobServiceClientBuilder
+import com.azure.storage.queue.QueueClient
+import com.azure.storage.queue.QueueServiceClientBuilder
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+class AzureConfig {
+
+ @Value("\${azure.storage.connection-string}")
+ private lateinit var connectionString: String
+
+ @Value("\${azure.storage.container-name}")
+ private lateinit var containerName: String
+
+ @Value("\${azure.storage.queue-name}")
+ private lateinit var queueName: String
+
+ @Value("\${azure.storage.table-name}")
+ private lateinit var tableName: String
+
+ @Bean
+ fun blobContainerClient(): BlobContainerClient {
+ return BlobServiceClientBuilder()
+ .connectionString(connectionString)
+ .buildClient()
+ .getBlobContainerClient(containerName)
+ }
+
+ @Bean
+ fun queueClient(): QueueClient {
+ return QueueServiceClientBuilder()
+ .connectionString(connectionString)
+ .buildClient()
+ .getQueueClient(queueName)
+ }
+
+ @Bean
+ fun tableClient(): TableClient {
+
+ val tableServiceClient = TableServiceClientBuilder()
+ .connectionString(connectionString)
+ .buildClient()
+
+ // Ensure the table is created if it does not exist
+ tableServiceClient.createTableIfNotExists(tableName)
+
+ return tableServiceClient.getTableClient(tableName)
+ }
+}
\ No newline at end of file
diff --git a/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt
new file mode 100644
index 00000000000..4c07d8c2858
--- /dev/null
+++ b/submissions/src/main/kotlin/gov/cdc/prime/reportstream/submissions/controllers/SubmissionController.kt
@@ -0,0 +1,247 @@
+package gov.cdc.prime.reportstream.submissions.controllers
+
+import com.azure.data.tables.TableClient
+import com.azure.data.tables.models.TableEntity
+import com.azure.storage.blob.BlobContainerClient
+import com.azure.storage.queue.QueueClient
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import gov.cdc.prime.reportstream.shared.SubmissionQueueMessage
+import gov.cdc.prime.reportstream.submissions.ReportReceivedEvent
+import gov.cdc.prime.reportstream.submissions.TelemetryService
+import org.slf4j.LoggerFactory
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.MissingRequestHeaderException
+import org.springframework.web.bind.annotation.ControllerAdvice
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestHeader
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.client.HttpClientErrorException.UnsupportedMediaType
+import java.io.IOException
+import java.time.Instant
+import java.util.UUID
+
+private val logger = LoggerFactory.getLogger(SubmissionController::class.java)
+
+/**
+ * Controller for handling report submissions.
+ *
+ * This controller provides an endpoint for submitting reports in various formats.
+ * The reports are processed and stored in Azure Blob Storage, queued for further processing,
+ * and their metadata is saved in Azure Table Storage.
+ */
+@RestController
+class SubmissionController(
+ private val blobContainerClient: BlobContainerClient,
+ private val queueClient: QueueClient,
+ private val tableClient: TableClient,
+ private val telemetryService: TelemetryService,
+) {
+ /**
+ * Submits a report.
+ *
+ * This endpoint accepts reports in HL7 V2 and FHIR NDJSON formats. The report data is uploaded
+ * to Azure Blob Storage, queued for further processing, and its metadata is stored in Azure Table Storage.
+ * A custom event is also tracked in Application Insights for monitoring.
+ *
+ * @param headers the HTTP headers of the request
+ * @param contentType the content type of the report (must be "application/hl7-v2" or "application/fhir+ndjson")
+ * @param clientId the ID of the client submitting the report. Should represent org.senderName
+ * @param data the report data
+ * @return a ResponseEntity containing the reportID, status, and timestamp
+ */
+ @PostMapping("/api/v1/reports", consumes = ["application/hl7-v2", "application/fhir+ndjson"])
+ fun submitReport(
+ @RequestHeader headers: Map,
+ @RequestHeader("Content-Type") contentType: String,
+ @RequestHeader("client_id") clientId: String,
+ @RequestHeader("content-length") contentLength: String,
+ @RequestHeader("x-azure-clientip") senderIp: String,
+ @RequestHeader(value = "payloadName", required = false) payloadName: String?,
+ @RequestBody data: String,
+ ): ResponseEntity<*> {
+ val reportId = UUID.randomUUID()
+ val reportReceivedTime = Instant.now()
+ val contentTypeMime = contentType.substringBefore(';')
+ val status = "Received"
+ val objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule())
+ logger.info(
+ "Received report submission: reportId=$reportId, contentType=$contentTypeMime" +
+ ", clientId=$clientId${payloadName?.let { ", payloadName=$it" } ?: ""}}"
+ )
+
+ // Convert data to ByteArray
+ val dataByteArray = data.toByteArray()
+ logger.debug("Converted report data to ByteArray")
+
+ // Upload to blob storage
+ val blobClient = blobContainerClient.getBlobClient(formBlobName(reportId, contentTypeMime, clientId))
+ blobClient.upload(dataByteArray.inputStream(), dataByteArray.size.toLong())
+ logger.info("Uploaded report to blob storage: blobUrl=${blobClient.blobUrl}")
+
+ // Insert into Table
+ // TableEntity() sets PartitionKey and RowKey. Both are required by azure and combine to create the PK
+ val tableEntity = TableEntity(reportReceivedTime.toString(), reportId.toString())
+ val tableProperties = mapOf(
+ "report_received_time" to reportReceivedTime.toString(),
+ "report_accepted_time" to reportReceivedTime.toString(), // Will be updated when the report is accepted
+ "report_id" to reportId.toString(),
+ "status" to status
+ )
+ tableClient.createEntity(tableEntity.setProperties(tableProperties))
+ logger.info("Inserted report into table storage: reportId=$reportId")
+
+ // Create and publish custom event
+ val reportReceivedEvent = ReportReceivedEvent(
+ timeStamp = reportReceivedTime,
+ reportId = reportId,
+ parentReportId = reportId,
+ rootReportId = reportId,
+ headers = filterHeaders(headers),
+ sender = clientId,
+ senderIP = senderIp,
+ fileSize = contentLength,
+ blobUrl = blobClient.blobUrl
+ )
+ logger.debug("Created ReportReceivedEvent")
+
+ // Log to Application Insights
+ telemetryService.trackEvent(
+ "ReportReceivedEvent",
+ mapOf("event" to objectMapper.writeValueAsString(reportReceivedEvent)),
+ )
+ telemetryService.flush()
+ logger.info("Tracked ReportReceivedEvent with Application Insights")
+
+ // Queue upload should occur as the last step ensuring the other steps successfully process
+ // Create the message for the queue
+ val message = SubmissionQueueMessage(reportId, blobClient.blobUrl, filterHeaders(headers))
+ val messageString = objectMapper.writeValueAsString(message)
+ logger.debug("Created message for queue")
+
+ // Upload to Queue
+ queueClient.createIfNotExists()
+ queueClient.sendMessage(messageString)
+ logger.info("Sent message to queue: queueName=${queueClient.queueName}")
+
+ val response =
+ CreationResponse(
+ reportId,
+ status,
+ Instant.now(),
+ )
+ logger.info("Report submission successful: reportId=$reportId")
+
+ return ResponseEntity(response, HttpStatus.CREATED)
+ }
+
+ /**
+ * A centralized exception handler for handling exceptions that occur during report submission.
+ * This class uses Spring's @ControllerAdvice to catch and handle exceptions thrown by any controller in the application.
+ */
+ @ControllerAdvice
+ class SubmissionsExceptionHandler {
+
+ /**
+ * Handles exceptions of type UnsupportedMessageTypeException.
+ *
+ * @param e The UnsupportedMessageTypeException that was thrown.
+ * @return A ResponseEntity with an error message and HTTP status code 415 (Unsupported Media Type).
+ */
+ @ExceptionHandler(UnsupportedMediaType::class)
+ fun handleUnsupportedMessageTypeException(e: UnsupportedMediaType): ResponseEntity {
+ logger.warn("Unsupported message type exception: ${e.message}")
+ return ResponseEntity("Unsupported Media Type: ${e.message}", HttpStatus.UNSUPPORTED_MEDIA_TYPE)
+ }
+
+ /**
+ * Handles any runtime exception that occurs during report submission.
+ *
+ * @param e The runtime exception that was thrown.
+ * @return A ResponseEntity with an error message and HTTP status code 500 (Internal Server Error).
+ */
+ @ExceptionHandler(RuntimeException::class)
+ fun handleRuntimeException(e: RuntimeException): ResponseEntity {
+ // Log the error message and stack trace for debugging purposes
+ logger.error("Runtime exception during report submission: ${e.message}", e)
+
+ // Return a response entity with a generic error message and internal server error status
+ return ResponseEntity("Internal Server Error: ${e.message}", HttpStatus.INTERNAL_SERVER_ERROR)
+ }
+
+ /**
+ * Handles exceptions of type IllegalArgumentException.
+ *
+ * @param e The IllegalArgumentException that was thrown.
+ * @return A ResponseEntity with an error message and HTTP status code 400 (Bad Request).
+ */
+ @ExceptionHandler(IllegalArgumentException::class)
+ fun handleIllegalArgumentException(e: IllegalArgumentException): ResponseEntity {
+ // Log a warning message indicating the bad request
+ logger.warn("Illegal argument exception: ${e.message}")
+
+ // Return a response entity with a specific error message and bad request status
+ return ResponseEntity("Bad Request: ${e.message}", HttpStatus.BAD_REQUEST)
+ }
+
+ /**
+ * Handles any IO exception that occurs during report submission.
+ *
+ * @param e The IO exception that was thrown.
+ * @return A ResponseEntity with an error message and HTTP status code 500 (Internal Server Error).
+ */
+ @ExceptionHandler(IOException::class)
+ fun handleIOException(e: IOException): ResponseEntity {
+ // Log the error message and stack trace for debugging purposes
+ logger.error("IO exception during report submission: ${e.message}", e)
+
+ // Return a response entity with a generic error message and internal server error status
+ return ResponseEntity("Internal Server Error: ${e.message}", HttpStatus.INTERNAL_SERVER_ERROR)
+ }
+
+ /**
+ * Handles exceptions of type MissingRequestHeaderException.
+ *
+ * @param e The MissingRequestHeaderException that was thrown.
+ * @return A ResponseEntity with an error message and HTTP status code 400 (Bad Request).
+ */
+ @ExceptionHandler(MissingRequestHeaderException::class)
+ fun handleMissingRequestHeaderException(e: MissingRequestHeaderException): ResponseEntity {
+ // Log a warning message indicating the missing header
+ logger.warn("Missing request header exception: ${e.message}")
+
+ // Return a response entity with a specific error message and bad request status
+ return ResponseEntity("Bad Request: Missing required header: ${e.headerName}", HttpStatus.BAD_REQUEST)
+ }
+ }
+
+ private fun filterHeaders(headers: Map): Map {
+ val headersToInclude = listOf("client_id", "Content-Type", "payloadname", "x-azure-clientip")
+ return headers.filter { it.key in headersToInclude }
+ }
+
+ private fun formBlobName(
+ reportId: UUID,
+ contentTypeMime: String,
+ clientId: String,
+ ): String {
+ val senderName = clientId.lowercase()
+ return when (contentTypeMime.lowercase()) {
+ "application/hl7-v2" -> "receive/$senderName/$reportId.hl7"
+ "application/fhir+ndjson" -> "receive/$senderName/$reportId.fhir"
+ else -> throw IllegalArgumentException("Unsupported content-type: $this")
+ }
+ }
+}
+
+/**
+ * Data class representing the response for a successful report creation.
+ *
+ * @property reportId the unique ID of the report
+ * @property overallStatus the overall status of the report submission
+ * @property timestamp the timestamp when the report was received
+ */
+data class CreationResponse(val reportId: UUID, val overallStatus: String, val timestamp: Instant)
\ No newline at end of file
diff --git a/submissions/src/main/resources/application.properties b/submissions/src/main/resources/application.properties
index ec52707f738..466020a58e1 100644
--- a/submissions/src/main/resources/application.properties
+++ b/submissions/src/main/resources/application.properties
@@ -1,2 +1,6 @@
spring.application.name=submissions
server.port=8880
+azure.storage.connection-string=${AZURE_STORAGE_CONNECTION_STRING:DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;}
+azure.storage.container-name=${AZURE_STORAGE_CONTAINER_NAME:reports}
+azure.storage.queue-name=${AZURE_STORAGE_QUEUE_NAME:elr-fhir-convert}
+azure.storage.table-name=${AZURE_STORAGE_TABLE_NAME:submissions}
\ No newline at end of file
diff --git a/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt
new file mode 100644
index 00000000000..400914ca331
--- /dev/null
+++ b/submissions/src/test/kotlin/SubmissionControllerIntegrationTest.kt
@@ -0,0 +1,134 @@
+package gov.cdc.prime.reportstream.submissions.controllers
+
+import com.azure.core.util.Context
+import com.azure.storage.blob.models.BlobItem
+import com.azure.storage.queue.models.PeekedMessageItem
+import com.azure.data.tables.TableClient
+import com.azure.data.tables.models.TableEntity
+import com.azure.storage.blob.BlobContainerClient
+import com.azure.storage.queue.QueueClient
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.MediaType
+import org.springframework.test.context.ActiveProfiles
+import org.springframework.test.context.DynamicPropertyRegistry
+import org.springframework.test.context.DynamicPropertySource
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers
+import org.testcontainers.containers.GenericContainer
+import org.testcontainers.utility.DockerImageName
+import java.time.Duration
+
+@SpringBootTest
+@ActiveProfiles("test")
+@AutoConfigureMockMvc
+class SubmissionControllerIntegrationTest {
+
+ @Autowired
+ private lateinit var mockMvc: MockMvc
+
+ @Autowired
+ private lateinit var blobContainerClient: BlobContainerClient
+
+ @Autowired
+ private lateinit var queueClient: QueueClient
+
+ @Autowired
+ private lateinit var tableClient: TableClient
+
+
+ private lateinit var objectMapper: ObjectMapper
+
+ companion object {
+ private val azuriteContainer = GenericContainer(
+ DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite")
+ )
+ .withExposedPorts(10000, 10001, 10002)
+
+// @JvmStatic
+ @DynamicPropertySource
+ fun dynamicProperties(registry: DynamicPropertyRegistry) {
+ azuriteContainer.start()
+ val blobEndpoint = "http://${azuriteContainer.host}:${azuriteContainer.getMappedPort(10000)}"
+ val queueEndpoint = "http://${azuriteContainer.host}:${azuriteContainer.getMappedPort(10001)}"
+ val tableEndpoint = "http://${azuriteContainer.host}:${azuriteContainer.getMappedPort(10002)}"
+ val connectionString = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vd" +
+ "M02xNOcqFeW5vUwJ2F1rU0A8NHU6eT9+iU7Jk+E6i0z4lIFIKVzjze7d4i/ASTQ5C1R9F6rmTDL6wFg==;" +
+ "BlobEndpoint=$blobEndpoint/devstoreaccount1;QueueEndpoint=$queueEndpoint;" +
+ "TableEndpoint=$tableEndpoint/devstoreaccount1;"
+
+ registry.add("azure.storage.connection-string") { connectionString }
+ }
+ }
+
+ @BeforeEach
+ fun setUp() {
+ objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule())
+
+ if (!blobContainerClient.exists()) {
+ blobContainerClient.create()
+ }
+ }
+
+ @AfterEach
+ fun tearDown() {
+ blobContainerClient.deleteIfExists()
+ queueClient.clearMessages()
+ tableClient.deleteTable()
+ }
+
+ @Test
+ fun `submitReport should return CREATED status and store data in Azurite`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.valueOf("application/hl7-v2"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isCreated)
+
+ // Verify blob was uploaded and read its content
+ val blobs: MutableList = mutableListOf()
+ blobContainerClient.listBlobs().iterator().forEachRemaining { blobs.add(it) }
+ assertEquals(1, blobs.size)
+ val blobContent = blobContainerClient.getBlobClient(blobs[0].name).downloadContent().toString()
+ assertEquals(requestBody, blobContent)
+
+ // Verify message was sent to queue and read its content
+ val messages: MutableList = mutableListOf()
+ queueClient.peekMessages(
+ 10,
+ Duration.ofSeconds(30),
+ Context.NONE
+ ).iterator().forEachRemaining { messages.add(it) }
+ assertEquals(1, messages.size)
+
+ val queueMessageContent = objectMapper.readValue(messages[0].body.toString(), Map::class.java) as Map<*, *>
+ val headers = queueMessageContent["headers"] as Map<*, *>
+ assertEquals("testClient", headers["client_id"])
+ assertEquals("application/hl7-v2;charset=UTF-8", headers["Content-Type"])
+ assertEquals("testPayload", headers["payloadname"])
+ assertEquals("127.0.0.1", headers["x-azure-clientip"])
+
+ // Verify entity was added to table and read its content
+ val entities: MutableList = mutableListOf()
+ tableClient.listEntities().iterator().forEachRemaining { entities.add(it) }
+ assertEquals(1, entities.size)
+ val tableEntity = entities[0]
+ assertEquals("Received", tableEntity.getProperty("status"))
+ }
+}
diff --git a/submissions/src/test/kotlin/SubmissionControllerTest.kt b/submissions/src/test/kotlin/SubmissionControllerTest.kt
new file mode 100644
index 00000000000..45a725915c5
--- /dev/null
+++ b/submissions/src/test/kotlin/SubmissionControllerTest.kt
@@ -0,0 +1,334 @@
+package gov.cdc.prime.reportstream.submissions.controllers
+
+import com.azure.data.tables.TableClient
+import com.azure.storage.blob.BlobClient
+import com.azure.storage.blob.BlobContainerClient
+import com.azure.storage.queue.QueueClient
+import com.azure.storage.queue.models.SendMessageResult
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.microsoft.applicationinsights.TelemetryClient
+import gov.cdc.prime.reportstream.submissions.TelemetryService
+import gov.cdc.prime.reportstream.submissions.config.AzureConfig
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyMap
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.mockStatic
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.argumentCaptor
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.boot.test.mock.mockito.MockBean
+import org.springframework.context.annotation.Import
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import java.util.UUID
+
+@WebMvcTest(SubmissionController::class)
+@Import(AzureConfig::class)
+class SubmissionControllerTest {
+
+ @Autowired
+ private lateinit var mockMvc: MockMvc
+
+ @MockBean
+ private lateinit var blobContainerClient: BlobContainerClient
+
+ @MockBean
+ private lateinit var queueClient: QueueClient
+
+ @MockBean
+ private lateinit var tableClient: TableClient
+
+ @MockBean
+ private lateinit var telemetryService: TelemetryService
+
+ private lateinit var objectMapper: ObjectMapper
+
+ private lateinit var blobClient: BlobClient
+ private lateinit var sendMessageResult: SendMessageResult
+
+ @BeforeEach
+ fun setUp() {
+ MockitoAnnotations.openMocks(this)
+
+ objectMapper = jacksonObjectMapper().registerModule(JavaTimeModule())
+
+ // Mock BlobContainerClient and BlobClient
+ blobClient = mock(BlobClient::class.java)
+ `when`(blobContainerClient.getBlobClient(anyString())).thenReturn(blobClient)
+
+ // Ensure blobUrl is mocked properly
+ `when`(blobClient.blobUrl).thenReturn("https://example.com/blobUrl")
+
+ // Mock the upload method
+ doNothing().`when`(blobClient).upload(any(ByteArrayInputStream::class.java), anyLong())
+
+ // Mock QueueClient
+ sendMessageResult = mock(SendMessageResult::class.java)
+ `when`(queueClient.sendMessage(anyString())).thenReturn(sendMessageResult)
+
+ // Mock the table createEntity method
+ doNothing().`when`(tableClient).createEntity(any())
+
+ // Ensure telemetryService methods are mocked
+ doNothing().`when`(telemetryService).trackEvent(anyString(), anyMap())
+ doNothing().`when`(telemetryService).flush()
+ }
+
+ @AfterEach
+ fun tearDown() {
+ // Reset all mocks after each test to ensure isolation
+ reset(
+ blobClient,
+ blobContainerClient,
+ queueClient,
+ tableClient,
+ telemetryService,
+ )
+ }
+
+ @Test
+ fun `submitReport should return CREATED status with content-type hl7-v2 and correct arguments`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+ val expectedBlobUrl = "https://example.com/blobUrl"
+
+ // Mock the UUID generation to ensure a predictable report ID
+ val reportId = UUID.randomUUID()
+ val uuidMockedStatic = mockStatic(UUID::class.java)
+ uuidMockedStatic.`when` { UUID.randomUUID() }.thenReturn(reportId)
+
+ // Capture the arguments passed to the upload and sendMessage methods
+ val blobInputStreamCaptor = argumentCaptor()
+ val blobSizeCaptor = argumentCaptor()
+ val messageCaptor = argumentCaptor()
+
+ `when`(blobClient.blobUrl).thenReturn(expectedBlobUrl)
+ `when`(queueClient.sendMessage(anyString())).thenReturn(sendMessageResult)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.valueOf("application/hl7-v2"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isCreated)
+
+ verify(blobClient).upload(blobInputStreamCaptor.capture(), blobSizeCaptor.capture())
+ verify(queueClient).sendMessage(messageCaptor.capture())
+
+ // Assert the captured arguments
+ assert(blobSizeCaptor.firstValue == requestBody.length.toLong())
+ val capturedMessage = objectMapper.readValue(messageCaptor.firstValue, Map::class.java)
+ assert(capturedMessage["reportId"] == reportId.toString())
+ assert(capturedMessage["blobUrl"] == expectedBlobUrl)
+ assert(capturedMessage["headers"] != null)
+ val headers = capturedMessage["headers"] as Map<*, *>
+ assert(headers["client_id"] == "testClient")
+ assert(headers["Content-Type"] == "application/hl7-v2;charset=UTF-8")
+ assert(headers["payloadname"] == "testPayload")
+
+ uuidMockedStatic.close()
+ }
+
+ @Test
+ fun `submitReport should return CREATED status with content-type fhir+ndjson and correct arguments`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+ val expectedBlobUrl = "https://example.com/blobUrl"
+
+ // Mock the UUID generation to ensure a predictable report ID
+ val reportId = UUID.randomUUID()
+
+ val uuidMockedStatic = mockStatic(UUID::class.java)
+ uuidMockedStatic.`when` { UUID.randomUUID() }.thenReturn(reportId)
+
+ // Capture the arguments passed to the upload and sendMessage methods
+ val blobInputStreamCaptor = argumentCaptor()
+ val blobSizeCaptor = argumentCaptor()
+ val messageCaptor = argumentCaptor()
+
+ `when`(blobClient.blobUrl).thenReturn(expectedBlobUrl)
+ `when`(queueClient.sendMessage(anyString())).thenReturn(sendMessageResult)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.valueOf("application/fhir+ndjson"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isCreated)
+
+ verify(blobClient).upload(blobInputStreamCaptor.capture(), blobSizeCaptor.capture())
+ verify(queueClient).sendMessage(messageCaptor.capture())
+
+ // Assert the captured arguments
+ assert(blobSizeCaptor.firstValue == requestBody.length.toLong())
+ val capturedMessage = objectMapper.readValue(messageCaptor.firstValue, Map::class.java)
+ assert(capturedMessage["reportId"] == reportId.toString())
+ assert(capturedMessage["blobUrl"] == expectedBlobUrl)
+ assert(capturedMessage["headers"] != null)
+ val headers = capturedMessage["headers"] as Map<*, *>
+ assert(headers["client_id"] == "testClient")
+ assert(headers["Content-Type"] == "application/fhir+ndjson;charset=UTF-8")
+ assert(headers["payloadname"] == "testPayload")
+
+ uuidMockedStatic.close()
+ }
+
+ @Test
+ fun `submitReport should return UNSUPPORTED_MEDIA_TYPE status with unsupported content-type`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.APPLICATION_JSON)
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isUnsupportedMediaType)
+ }
+
+ @Test
+ fun `submitReport should return BAD_REQUEST status when client_id is missing`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.valueOf("application/hl7-v2"))
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isBadRequest)
+ .andExpect(
+ MockMvcResultMatchers.content()
+ .string("Bad Request: Missing required header: client_id")
+ )
+ }
+
+ @Test
+ fun `submitReport should return INTERNAL_SERVER_ERROR on blob storage failure`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+
+ doThrow(RuntimeException("Blob storage failure"))
+ .`when`(blobClient).upload(any(ByteArrayInputStream::class.java), anyLong())
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.parseMediaType("application/hl7-v2"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isInternalServerError)
+ .andExpect(MockMvcResultMatchers.content().string("Internal Server Error: Blob storage failure"))
+
+ verify(blobClient).upload(any(ByteArrayInputStream::class.java), anyLong())
+ }
+
+ @Test
+ fun `submitReport should return INTERNAL_SERVER_ERROR on queue service failure`() {
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+
+ doNothing().`when`(blobClient).upload(any(ByteArrayInputStream::class.java), anyLong())
+ doThrow(RuntimeException("Queue service failure")).`when`(queueClient).sendMessage(anyString())
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.parseMediaType("application/hl7-v2"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isInternalServerError)
+
+ verify(blobClient).upload(any(ByteArrayInputStream::class.java), anyLong())
+ verify(queueClient).sendMessage(anyString())
+ }
+
+ @Test
+ fun `submitReport should log ReportReceivedEvent with correct details`() {
+ // Helper function to safely cast the captured map to Map
+ fun mapToStringString(input: Map<*, *>): Map {
+ return input.mapNotNull { (key, value) ->
+ val stringKey = key as? String
+ val stringValue = value as? String
+ if (stringKey != null && stringValue != null) {
+ stringKey to stringValue
+ } else {
+ null
+ }
+ }.toMap()
+ }
+
+ val data = mapOf("key" to "value")
+ val requestBody = objectMapper.writeValueAsString(data)
+ val expectedBlobUrl = "https://example.com/blobUrl"
+ val reportId = UUID.randomUUID()
+
+ // Mock the UUID generation to ensure a predictable report ID
+ val uuidMockedStatic = mockStatic(UUID::class.java)
+ uuidMockedStatic.`when` { UUID.randomUUID() }.thenReturn(reportId)
+
+ mockMvc.perform(
+ MockMvcRequestBuilders.post("/api/v1/reports")
+ .content(requestBody)
+ .contentType(MediaType.valueOf("application/hl7-v2"))
+ .header("client_id", "testClient")
+ .header("payloadname", "testPayload")
+ .header("x-azure-clientip", "127.0.0.1")
+ )
+ .andExpect(MockMvcResultMatchers.status().isCreated)
+
+ val eventCaptor = argumentCaptor()
+ val propertiesCaptor = argumentCaptor