Skip to content

Commit

Permalink
Support multiple codings and categories per observation
Browse files Browse the repository at this point in the history
  • Loading branch information
vishnuravi committed May 23, 2024
1 parent ea25dfc commit 43a9688
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.health.connect.client.records.BloodPressureRecord
import androidx.health.connect.client.records.BodyTemperatureRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.HeightRecord
import androidx.health.connect.client.records.Record
import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.WeightRecord
import org.hl7.fhir.r4.model.CodeableConcept
Expand All @@ -14,21 +15,30 @@ import org.hl7.fhir.r4.model.Period
import org.hl7.fhir.r4.model.Quantity
import java.util.Date

fun <T> T.toObservation(
code: String,
display: String,
fun <T: Record> T.createObservation(
categories: List<Coding>? = null,
codings: List<Coding>,
unit: String,
valueExtractor: T.() -> Double,
periodExtractor: T.() -> Pair<Date, Date>
): Observation {
val observation = Observation()
observation.status = Observation.ObservationStatus.FINAL
observation.code = CodeableConcept().addCoding(
Coding()
.setSystem("http://loinc.org")
.setCode(code)
.setDisplay(display)
)

if (categories != null) {
val categoryConcept = CodeableConcept()
categories.forEach { coding ->
categoryConcept.addCoding(coding)
}
observation.addCategory(categoryConcept)
}

val codeableConcept = CodeableConcept()
codings.forEach { coding ->
codeableConcept.addCoding(coding)
}

observation.code = codeableConcept

val period = Period()
val (start, end) = this.periodExtractor()
Expand All @@ -42,38 +52,85 @@ fun <T> T.toObservation(
}

inline fun StepsRecord.toObservation(): Observation {
return this.toObservation(
code = "55423-8",
display = "Number of steps",
return this.createObservation(
categories = listOf(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("activity").setDisplay("Activity")
),
codings = listOf(
Coding().setSystem("http://loinc.org").setCode("55423-8").setDisplay("Number of steps")
),
unit = "steps",
valueExtractor = { count.toDouble() },
periodExtractor = { Date.from(startTime) to Date.from(endTime) }
)
}

inline fun WeightRecord.toObservation(): Observation {
return this.toObservation(
code = "29463-7",
display = "Body weight",
return this.createObservation(
categories = listOf(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Vital Signs")
),
codings = listOf(
Coding().setSystem("http://loinc.org").setCode("29463-7").setDisplay("Body weight")
),
unit = "g",
valueExtractor = { weight.inGrams },
periodExtractor = { Date.from(time) to Date.from(time) }
)
}

inline fun HeightRecord.toObservation(): Observation {
return this.createObservation(
categories = listOf(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Vital Signs")
),
codings = listOf(
Coding().setSystem("http://loinc.org").setCode("8302-2").setDisplay("Body height")
),
unit = "m",
valueExtractor = { height.inMeters },
periodExtractor = { Date.from(time) to Date.from(time) }
)
}

inline fun ActiveCaloriesBurnedRecord.toObservation(): Observation {
return this.toObservation(
code = "41981-2",
display = "Calories burned",
return this.createObservation(
categories = listOf(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("activity").setDisplay("Activity")
),
codings = listOf(
Coding().setSystem("http://loinc.org").setCode("41981-2").setDisplay("Calories burned")
),
unit = "kcal",
valueExtractor = { energy.inCalories },
periodExtractor = { Date.from(startTime) to Date.from(endTime) }
)
}

inline fun BodyTemperatureRecord.toObservation(): Observation {
return createObservation(
categories = listOf(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Vital Signs")
),
codings = listOf(
Coding().setSystem("http://loinc.org").setCode("8310-5").setDisplay("Body temperature")
),
unit = "°C",
valueExtractor = { temperature.inCelsius },
periodExtractor = { Date.from(time) to Date.from(time) }
)
}

inline fun BloodPressureRecord.toObservation(): Observation {
val observation = Observation()
observation.status = Observation.ObservationStatus.FINAL

observation.category = listOf(
CodeableConcept().addCoding(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Vital Signs")
)
)

observation.code = CodeableConcept().addCoding(
Coding()
.setSystem("http://loinc.org")
Expand Down Expand Up @@ -110,20 +167,17 @@ inline fun BloodPressureRecord.toObservation(): Observation {
return observation
}

inline fun HeightRecord.toObservation(): Observation {
return this.toObservation(
code = "8302-2",
display = "Body height",
unit = "m",
valueExtractor = { height.inMeters },
periodExtractor = { Date.from(time) to Date.from(time) }
)
}

inline fun HeartRateRecord.toObservations(): List<Observation> {
return samples.map { sample ->
val observation = Observation()
observation.status = Observation.ObservationStatus.FINAL

observation.category = listOf(
CodeableConcept().addCoding(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Vital Signs")
)
)

observation.code = CodeableConcept().addCoding(
Coding()
.setSystem("http://loinc.org")
Expand All @@ -140,14 +194,4 @@ inline fun HeartRateRecord.toObservations(): List<Observation> {

observation
}
}

inline fun BodyTemperatureRecord.toObservation(): Observation {
return this.toObservation(
code = "8310-5",
display = "Body temperature",
unit = "°C",
valueExtractor = { temperature.inCelsius },
periodExtractor = { Date.from(time) to Date.from(time) }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.junit.Test

import org.junit.Assert.*
import java.time.Instant
import java.time.ZoneOffset
import java.util.Date

/**
Expand All @@ -42,8 +43,8 @@ class CreateObservationTests {
count = 1000,
startTime = Instant.parse("2023-05-18T10:15:30.00Z"),
endTime = Instant.parse("2023-05-18T11:15:30.00Z"),
startZoneOffset = null,
endZoneOffset = null
startZoneOffset = ZoneOffset.UTC,
endZoneOffset = ZoneOffset.UTC
)

val observation = stepsRecord.toObservation()
Expand All @@ -52,6 +53,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("55423-8", observation.code.codingFirstRep.code)
assertEquals("activity", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand All @@ -69,7 +71,7 @@ class CreateObservationTests {
val weightRecord = WeightRecord(
weight = Mass.kilograms(75.0),
time = Instant.parse("2023-05-18T10:15:30.00Z"),
zoneOffset = null
zoneOffset = ZoneOffset.UTC
)

val observation = weightRecord.toObservation()
Expand All @@ -78,6 +80,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("29463-7", observation.code.codingFirstRep.code)
assertEquals("vital-signs", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand All @@ -96,8 +99,8 @@ class CreateObservationTests {
energy = Energy.calories(250.0),
startTime = Instant.parse("2023-05-18T10:15:30.00Z"),
endTime = Instant.parse("2023-05-18T11:15:30.00Z"),
startZoneOffset = null,
endZoneOffset = null
startZoneOffset = ZoneOffset.UTC,
endZoneOffset = ZoneOffset.UTC
)

val observation = activeCaloriesBurnedRecord.toObservation()
Expand All @@ -106,6 +109,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("41981-2", observation.code.codingFirstRep.code)
assertEquals("activity", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand All @@ -124,7 +128,7 @@ class CreateObservationTests {
time = Instant.parse("2023-05-18T10:15:30.00Z"),
systolic = Pressure.millimetersOfMercury(120.0),
diastolic = Pressure.millimetersOfMercury(80.0),
zoneOffset = null
zoneOffset = ZoneOffset.UTC
)

val observation = bloodPressureRecord.toObservation()
Expand All @@ -133,6 +137,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("85354-9", observation.code.codingFirstRep.code)
assertEquals("vital-signs", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand All @@ -152,7 +157,7 @@ class CreateObservationTests {
val heightRecord = HeightRecord(
time = Instant.parse("2023-05-18T10:15:30.00Z"),
height = Length.meters(1.75),
zoneOffset = null
zoneOffset = ZoneOffset.UTC
)

val observation = heightRecord.toObservation()
Expand All @@ -161,6 +166,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("8302-2", observation.code.codingFirstRep.code)
assertEquals("vital-signs", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand All @@ -178,7 +184,7 @@ class CreateObservationTests {
val bodyTemperatureRecord = BodyTemperatureRecord(
time = Instant.parse("2023-05-18T10:15:30.00Z"),
temperature = Temperature.celsius(37.5),
zoneOffset = null
zoneOffset = ZoneOffset.UTC
)

val observation = bodyTemperatureRecord.toObservation()
Expand All @@ -187,6 +193,7 @@ class CreateObservationTests {

assertEquals(Observation.ObservationStatus.FINAL, observation.status)
assertEquals("8310-5", observation.code.codingFirstRep.code)
assertEquals("vital-signs", observation.categoryFirstRep.codingFirstRep.code)
assertEquals(
Date.from(Instant.parse("2023-05-18T10:15:30.00Z")),
(observation.effective as Period).start
Expand Down

0 comments on commit 43a9688

Please sign in to comment.