Skip to content

Commit

Permalink
stamping oid for each condition rather than for each observation
Browse files Browse the repository at this point in the history
  • Loading branch information
kant committed Dec 4, 2024
1 parent cffc793 commit d14dae0
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 47 deletions.
69 changes: 41 additions & 28 deletions prime-router/src/main/kotlin/azure/ConditionMapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,23 @@ class LookupTableConditionMapper(metadata: Metadata) : IConditionMapper {
}

override fun lookupMemberOid(codings: List<Coding>): Map<String, String> {
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
}
}
}

Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
)
)
)
Expand All @@ -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")
}
}

Expand Down

0 comments on commit d14dae0

Please sign in to comment.