diff --git a/prime-router/src/main/kotlin/azure/ConditionMapper.kt b/prime-router/src/main/kotlin/azure/ConditionMapper.kt index 2ab18dc3b0c..df3c0b9c12e 100644 --- a/prime-router/src/main/kotlin/azure/ConditionMapper.kt +++ b/prime-router/src/main/kotlin/azure/ConditionMapper.kt @@ -44,16 +44,23 @@ class LookupTableConditionMapper(metadata: Metadata) : IConditionMapper { } override fun lookupMemberOid(codings: List): Map { - return mappingTable.FilterBuilder() - .isIn(ObservationMappingConstants.TEST_CODE_KEY, codings.map { it.code }) - .filter().caseSensitiveDataRowsMap.fold(mutableMapOf()) { acc, condition -> - val testCode = condition[ObservationMappingConstants.TEST_CODE_KEY] ?: "" - val memberOid = condition[ObservationMappingConstants.TEST_OID_KEY] ?: "" - if (testCode.isNotEmpty() && memberOid.isNotEmpty()) { - acc[testCode] = memberOid - } - acc + // Extract condition codes using the mapping table, not directly from codings + val testCodes = codings.mapNotNull { it.code } // These are the input test codes + + // Filter rows related to condition mappings based on test codes + val filteredRows = mappingTable.FilterBuilder() + .isIn(ObservationMappingConstants.TEST_CODE_KEY, testCodes) // Map test codes to conditions + .filter().caseSensitiveDataRowsMap + + // Create a map of condition codes to member OIDs + return filteredRows.fold(mutableMapOf()) { acc, condition -> + val conditionCode = condition[ObservationMappingConstants.CONDITION_CODE_KEY] ?: "" + val memberOid = condition[ObservationMappingConstants.TEST_OID_KEY] ?: "" + if (conditionCode.isNotEmpty() && memberOid.isNotEmpty()) { + acc[conditionCode] = memberOid } + acc + } } } @@ -81,42 +88,48 @@ class ConditionStamper(private val conditionMapper: IConditionMapper) { * @return a [ObservationStampingResult] including stamping success and any mapping failures */ fun stampObservation(observation: Observation): ObservationStampingResult { + // Extract codes and filter out empty values val codeSourcesMap = observation.getCodeSourcesMap().filterValues { it.isNotEmpty() } if (codeSourcesMap.values.flatten().isEmpty()) return ObservationStampingResult(false) - // Lookup conditions and Member OIDs + // Lookup conditions mapped to codes val conditionsToCode = conditionMapper.lookupConditions(codeSourcesMap.values.flatten()) + + // Map test codes to member OIDs val memberOidMap = conditionMapper.lookupMemberOid(codeSourcesMap.values.flatten()) var mappedSomething = false - // Process condition mappings + // Process condition mappings for each code val failures = codeSourcesMap.mapNotNull { codes -> - val unnmapped = codes.value.mapNotNull { code -> + val unmapped = codes.value.mapNotNull { code -> val conditions = conditionsToCode.getOrDefault(code, emptyList()) if (conditions.isEmpty()) { + // If no conditions are mapped, add this code to failures code } else { - conditions.forEach { code.addExtension(CONDITION_CODE_EXTENSION_URL, it) } - mappedSomething = true + conditions.forEach { conditionCoding -> + // Create a condition-code extension + val conditionCodeExtension = Extension(CONDITION_CODE_EXTENSION_URL) + conditionCodeExtension.setValue(conditionCoding) + + // Retrieve and add the member OID as a sub-extension + val memberOid = memberOidMap[conditionCoding.code] + if (memberOid != null) { + val memberOidExtension = Extension(MEMBER_OID_EXTENSION_URL) + memberOidExtension.setValue(StringType(memberOid)) + conditionCodeExtension.addExtension(memberOidExtension) + } + + // Attach the condition-code extension to the coding + code.addExtension(conditionCodeExtension) + mappedSomething = true + } null } } - if (unnmapped.isEmpty()) null else ObservationMappingFailure(codes.key, unnmapped) + if (unmapped.isEmpty()) null else ObservationMappingFailure(codes.key, unmapped) } - - // Add the Member OID extension to the observation, based on the lookup - observation.code.coding.forEach { coding -> - val testCode = coding.code - val memberOid = memberOidMap[testCode] - if (memberOid != null) { - val memberOidExtension = Extension(MEMBER_OID_EXTENSION_URL) - memberOidExtension.setValue(StringType(memberOid)) - observation.addExtension(memberOidExtension) - mappedSomething = true - } - } - return ObservationStampingResult(mappedSomething, failures) } } \ No newline at end of file diff --git a/prime-router/src/test/kotlin/fhirengine/engine/FhirConverterTests.kt b/prime-router/src/test/kotlin/fhirengine/engine/FhirConverterTests.kt index 1e6182b91c7..43cdb669938 100644 --- a/prime-router/src/test/kotlin/fhirengine/engine/FhirConverterTests.kt +++ b/prime-router/src/test/kotlin/fhirengine/engine/FhirConverterTests.kt @@ -454,6 +454,7 @@ class FhirConverterTests { """{"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"}}}]}""" val memberOidExtensionURL = "https://reportstream.cdc.gov/fhir/StructureDefinition/test-performed-member-oid" + val conditionCodeExtensionURL = "https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code" metadata.lookupTableStore += mapOf( "observation-mapping" to LookupTable( @@ -467,18 +468,11 @@ class FhirConverterTests { ObservationMappingConstants.TEST_OID_KEY ), listOf( - "80382-5", - "6142004", - "SNOMEDCT", + "80382-5", // Test Code + "6142004", // Condition Code + "SNOMEDCT", // System "Influenza (disorder)", - "OID12345" - ), - listOf( - "260373001", - "Some Condition Code", - "Condition Code System", - "Condition Name", - "OID67890" + "OID12345" // OID ) ) ) @@ -492,19 +486,18 @@ class FhirConverterTests { // Add Condition and Member OID extensions ConditionStamper(LookupTableConditionMapper(metadata)).stampObservation(observation) - // Assert condition extensions + // Assert condition-code extension exists val conditionExtension = observation.code.coding[0].extension.find { - it.url == "https://reportstream.cdc.gov/fhir/StructureDefinition/condition-code" + it.url == conditionCodeExtensionURL } - assertNotNull(conditionExtension) - assertEquals("6142004", (conditionExtension!!.value as Coding).code) + assertNotNull("Condition-code extension not found.", conditionExtension) - // Assert Member OID extension - val memberOidExtension = observation.extension.find { + // Assert member OID sub-extension exists within condition-code extension + val oidSubExtension = conditionExtension!!.extension.find { it.url == memberOidExtensionURL } - assertNotNull(memberOidExtension) - assertEquals("OID12345", (memberOidExtension!!.value as StringType).value) + assertNotNull("Member OID sub-extension not found in condition-code extension.", oidSubExtension) + assertEquals("Member OID value does not match", (oidSubExtension!!.value as StringType).value, "OID12345") } }