Skip to content

Commit

Permalink
🐛 Update ConceptMapContext to support multiple mappings per source key
Browse files Browse the repository at this point in the history
  • Loading branch information
YemreGurses authored and sinaci committed Jan 20, 2025
1 parent e222e30 commit 7264ba3
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,46 @@ class MappingContextLoader extends IMappingContextLoader {

/**
* Read concept mappings from the given CSV file.
* Example dataset to understand what this function does:
* Input CSV content (concept mappings), assumed to be at some file path specified:
* -----------------------------
* source_code,target_code,display_value
* 001,A1,Foo
* 001,A2,Bar
* 002,B1,Baz
* -----------------------------
*
* Explanation of this structure:
* - "source_code" is the key (the first column header), which will group the rows.
* - "target_code" and "display_value" are part of the data for each key grouping.
*
* Expected output of processing:
* Map(
* "001" -> Seq(
* Map("source_code" -> "001", "target_code" -> "A1", "display_value" -> "Foo"),
* Map("source_code" -> "001", "target_code" -> "A2", "display_value" -> "Bar")
* ),
* "002" -> Seq(
* Map("source_code" -> "002", "target_code" -> "B1", "display_value" -> "Baz")
* )
* )
*
* @param filePath
* @return
*/
private def readConceptMapContextFromCSV(filePath: String): Future[Map[String, Map[String, String]]] = {
private def readConceptMapContextFromCSV(filePath: String): Future[Map[String, Seq[Map[String, String]]]] = {
readFromCSV(filePath) map {
case (columns, records) =>
//val (firstColumnName, _) = records.head.head // Get the first element in the records list and then get the first (k,v) pair to get the name of the first column.
records.foldLeft(Map[String, Map[String, String]]()) { (conceptMap, columnMap) =>
conceptMap + (columnMap(columns.head)-> columnMap)
val columnHeadKey = columns.head
records.foldLeft(Map[String, Seq[Map[String, String]]]()) { (conceptMap, columnMap) =>
val key = columnMap(columnHeadKey)
// If a source code has not been encountered before, add it as the first element.
// Otherwise, append the new target values to the existing sequence.
conceptMap.updatedWith(key) {
case Some(existingValues) => Some(existingValues :+ columnMap)
case None => Some(Seq(columnMap))
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,18 +132,13 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa
val evaluator = new FhirPathExpressionEvaluator(context, current)
// Should return the code of the concept whose mapping is requested
evaluator.visit(keyExpr) match {
case Nil =>
Nil
case Nil => Seq.empty
case Seq(FhirPathString(conceptCode)) =>
conceptMapContext
.concepts
.get(conceptCode)
.map {
case mws:Map[String, String] if mws.size == 1 => FhirPathString(mws.values.head)
case mws =>
FhirPathComplex(JObject(mws.toList.map(i => i._1 -> JString(i._2))))
}
.toSeq
.map(mws => mws.map(res => FhirPathComplex(JObject(res.toList.map(i => i._1 -> JString(i._2))))))
.getOrElse(Seq.empty)
case _ =>
throw new FhirPathException(s"Invalid function call 'getConcept', given expression for keyExpr:${keyExpr.getText} for the concept code should return a string value!")
}
Expand Down Expand Up @@ -191,21 +186,18 @@ class FhirPathMappingFunctions(context: FhirPathEnvironment, current: Seq[FhirPa
throw new FhirPathException(s"Invalid function call 'getConcept', given expression for keyExpr:${keyExpr.getText} for the concept code should return a string value!")
}
//If conceptCode returns empty, also return empty, if there is no such key or target column is null also return empty
val result =
val result: Seq[FhirPathResult] =
conceptCodeResult
.headOption
.map(_.asInstanceOf[FhirPathString].s) match {
case None => Nil
case Some(conceptCode) =>
conceptMapContext
.concepts
.get(conceptCode)
.flatMap(codeEntry =>
codeEntry.get(targetField).filter(_ != "")
)
.map(mappedValue => FhirPathString(mappedValue))
.toSeq
}
.headOption
.map(_.asInstanceOf[FhirPathString].s)
.flatMap { conceptCode =>
conceptMapContext.concepts.get(conceptCode).map { conceptMapEntries =>
conceptMapEntries
.flatMap(_.get(targetField))
.filter(_.nonEmpty)
.map(mappedValue => FhirPathString(mappedValue))
}
}.getOrElse(Seq.empty)
result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ trait FhirMappingContext {
* From a CSV who has a header like "source_code,source_system,source_display,unit,profile", and where
* source_code is 9110-8, the concepts Map can be as in the following:
*
* Map[9110-8 -> Map[(source_system -> http://loinc.org,Bleeding (cumulative)),
* (unit -> mL),
* (profile -> https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation)]]
* Map[9110-8 -> Seq [
* Map[(source_system -> http://loinc.org,Bleeding (cumulative)),
* (unit -> mL),
* (profile -> https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation)]
* ]]
*
*
*/
case class ConceptMapContext(concepts: Map[String, Map[String, String]]) extends FhirMappingContext {
case class ConceptMapContext(concepts: Map[String, Seq[Map[String, String]]]) extends FhirMappingContext {
override def toContextObject: JObject = JObject()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ source_code,source_system,source_display,unit,profile
445619006,http://snomed.info/sct,NEWS score,{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-NEWSScore
445597002,http://snomed.info/sct,PEWS score,{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-PEWSScore
9269-2,http://loinc.org,Galscow Coma Scale (GCS),{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-GlascowComaScaleObservation
1234-5,http://loinc.org,Hemogoblin,g/dL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
1234-5,http://loinc.org,Hemogoblin X,g/cL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
313002,http://www.nlm.nih.gov/research/umls/rxnorm,NaCl0.9% (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
35629,http://www.nlm.nih.gov/research/umls/rxnorm,Ringer (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
33834,http://www.nlm.nih.gov/research/umls/rxnorm,Plasmalyte (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ source_code,source_system,source_display,unit,profile
445619006,http://snomed.info/sct,NEWS score,{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-NEWSScore
445597002,http://snomed.info/sct,PEWS score,{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-PEWSScore
9269-2,http://loinc.org,Galscow Coma Scale (GCS),{score},https://aiccelerate.eu/fhir/StructureDefinition/AIC-GlascowComaScaleObservation
1234-5,http://loinc.org,Hemogoblin,g/dL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
1234-5,http://loinc.org,Hemogoblin X,g/cL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
313002,http://www.nlm.nih.gov/research/umls/rxnorm,NaCl0.9% (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
35629,http://www.nlm.nih.gov/research/umls/rxnorm,Ringer (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
33834,http://www.nlm.nih.gov/research/umls/rxnorm,Plasmalyte (cumulative) given,mL,https://aiccelerate.eu/fhir/StructureDefinition/AIC-MedicationAdministration
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,21 @@ class FhirMappingFolderRepositoryTest extends AsyncFlatSpec with ToFhirTestSpec
val mappingContextLoader = new MappingContextLoader
mappingContextLoader.retrieveContext(contextDefinition) map { context =>
val conceptMapContext = context.asInstanceOf[ConceptMapContext]
conceptMapContext.concepts.size shouldBe 13
conceptMapContext.concepts.size shouldBe 14

// source_code,source_system,source_display,unit,profile
// 9187-6,http://loinc.org,Urine Output,cm3,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
conceptMapContext.concepts("9187-6")("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("9187-6")("unit") shouldBe "cm3"
conceptMapContext.concepts("9187-6").head("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("9187-6").head("unit") shouldBe "cm3"

conceptMapContext.concepts("1234-5").length shouldBe 2
conceptMapContext.concepts("1234-5").head("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("1234-5").head("source_display") shouldBe "Hemogoblin"
conceptMapContext.concepts("1234-5").head("unit") shouldBe "g/dL"

conceptMapContext.concepts("1234-5")(1)("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("1234-5")(1)("source_display") shouldBe "Hemogoblin X"
conceptMapContext.concepts("1234-5")(1)("unit") shouldBe "g/cL"
}
}

Expand All @@ -60,12 +69,12 @@ class FhirMappingFolderRepositoryTest extends AsyncFlatSpec with ToFhirTestSpec
val mappingContextLoader = new MappingContextLoader
mappingContextLoader.retrieveContext(contextDefinition) map { context =>
val conceptMapContext = context.asInstanceOf[ConceptMapContext]
conceptMapContext.concepts.size shouldBe 13
conceptMapContext.concepts.size shouldBe 14

// source_code,source_system,source_display,unit,profile
// 9187-6,http://loinc.org,Urine Output,cm3,https://aiccelerate.eu/fhir/StructureDefinition/AIC-IntraOperativeObservation
conceptMapContext.concepts("9187-6")("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("9187-6")("unit") shouldBe "cm3"
conceptMapContext.concepts("9187-6").head("source_system") shouldBe "http://loinc.org"
conceptMapContext.concepts("9187-6").head("unit") shouldBe "cm3"
}
}

Expand Down

0 comments on commit 7264ba3

Please sign in to comment.