Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to request referenced resources along with the searched via inline params. #1978

Merged
merged 46 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
f33cc64
Inital changes to support inline resources via revInclude
aditya-07 Apr 13, 2023
54e4cbc
Merge branch 'master' into ak/revinclude
aditya-07 Apr 18, 2023
b0cd819
Merge branch 'master' into ak/revinclude
aditya-07 Apr 24, 2023
df863ac
Updated test and added doc
aditya-07 Apr 24, 2023
4239de9
Merge branch 'master' into ak/revinclude
aditya-07 Apr 27, 2023
e0f3c0f
RevInclude to return mapped response
aditya-07 Apr 28, 2023
1ccf108
Test code for forward include
aditya-07 May 4, 2023
d14e52a
Merge branch 'master' into ak/revinclude
aditya-07 May 5, 2023
543478c
Fixed include query
aditya-07 May 5, 2023
1818b80
Merge branch 'master' into ak/revinclude
aditya-07 Jun 6, 2023
724fb9d
Merge branch 'master' into ak/revinclude
aditya-07 Jun 28, 2023
379ed16
Unify search api to include include and revInclude functionality
aditya-07 Jun 29, 2023
c6f3682
Merge branch 'master' into ak/revinclude
aditya-07 Jun 29, 2023
75fac23
Workflow library: Incorporated changes related to FhirEngine.search
aditya-07 Jun 29, 2023
8ceb736
Merge branch 'ak/revinclude' of github.com:aditya-07/android-fhir int…
aditya-07 Jun 29, 2023
8756138
Merge branch 'master' into ak/revinclude
aditya-07 Jun 30, 2023
30d48a6
Merge branch 'master' into ak/revinclude
aditya-07 Jul 4, 2023
7acc7bb
Merge branch 'master' into ak/revinclude
aditya-07 Jul 5, 2023
4e50b9e
Merge branch 'master' into ak/revinclude
aditya-07 Jul 11, 2023
ced4b08
Merge branch 'master' into ak/revinclude
aditya-07 Jul 19, 2023
fe10fa0
Review comments: Changed the search result type to include the search…
aditya-07 Jul 20, 2023
5dcb352
Merge branch 'master' into ak/revinclude
aditya-07 Jul 20, 2023
7b55c7e
Updated docs
aditya-07 Jul 20, 2023
3db1d92
Added spotless toggle to skip indentation for specific code portions
aditya-07 Jul 20, 2023
e28c098
Merge branch 'master' into ak/revinclude
aditya-07 Jul 21, 2023
f74707e
Merge branch 'master' into ak/revinclude
aditya-07 Jul 24, 2023
35bf026
Added individual tests for include and revInclude
aditya-07 Jul 25, 2023
48df351
Merge branch 'master' into ak/revinclude
aditya-07 Jul 25, 2023
41d090c
Merge branch 'master' into ak/revinclude
aditya-07 Jul 26, 2023
b79bb28
Merge branch 'master' into ak/revinclude
aditya-07 Jul 26, 2023
a5ef271
Merge branch 'master' into ak/revinclude
aditya-07 Jul 27, 2023
286b58a
Merge branch 'master' into ak/revinclude
aditya-07 Jul 27, 2023
8efbdc2
Review Comments: Updated kdocs and refactored function name
aditya-07 Jul 27, 2023
af7768e
Merge branch 'master' into ak/revinclude
aditya-07 Jul 31, 2023
cfe459f
Review comments: refactored type of revInclude
aditya-07 Aug 1, 2023
31834c6
Fixed failing workflow compilation as workflow doesn't depends on eng…
aditya-07 Aug 1, 2023
9f169f3
Resolved compilation errors
aditya-07 Aug 1, 2023
fd22291
Merge branch 'master' into ak/revinclude
aditya-07 Aug 3, 2023
d5d0fe0
Merge branch 'master' into ak/revinclude
aditya-07 Aug 3, 2023
ed8a645
Merge branch 'master' into ak/revinclude
aditya-07 Aug 4, 2023
1228211
Merge branch 'master' into ak/revinclude
aditya-07 Aug 10, 2023
14ffa49
Merge branch 'master' into ak/revinclude
aditya-07 Aug 11, 2023
1dbe6be
Reverted the engine changes
aditya-07 Aug 11, 2023
530a5e1
Merge branch 'master' into ak/revinclude
aditya-07 Aug 11, 2023
8740ff4
Merge branch 'master' into ak/revinclude
omarismail94 Aug 21, 2023
00b1956
Merge branch 'master' into ak/revinclude
omarismail94 Aug 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/SpotlessConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Google LLC
* Copyright 2022-2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@ fun Project.configureSpotless() {
// It is necessary to tell spotless the top level of a file in order to apply config to it
// See: https://github.com/diffplug/spotless/issues/135
)
toggleOffOn()
}
kotlinGradle {
target("*.gradle.kts")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class FhirApplication : Application(), DataCaptureConfig.Provider {
dataCaptureConfig =
DataCaptureConfig().apply {
urlResolver = ReferenceUrlResolver(this@FhirApplication as Context)
xFhirQueryResolver = XFhirQueryResolver { fhirEngine.search(it) }
xFhirQueryResolver = XFhirQueryResolver { fhirEngine.search(it).map { it.resource } }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.get
import com.google.android.fhir.logicalId
import com.google.android.fhir.search.revInclude
import com.google.android.fhir.search.search
import java.text.SimpleDateFormat
import java.time.LocalDate
Expand All @@ -40,6 +40,8 @@ import org.apache.commons.lang3.StringUtils
import org.hl7.fhir.r4.model.Condition
import org.hl7.fhir.r4.model.Observation
import org.hl7.fhir.r4.model.Patient
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.RiskAssessment
import org.hl7.fhir.r4.model.codesystems.RiskProbability

Expand All @@ -59,108 +61,131 @@ class PatientDetailsViewModel(
viewModelScope.launch { livePatientData.value = getPatientDetailDataModel() }
}

private suspend fun getPatient(): PatientListViewModel.PatientItem {
val patient = fhirEngine.get<Patient>(patientId)
return patient.toPatientItem(0)
}
private suspend fun getPatientDetailDataModel(): List<PatientDetailData> {
val searchResult =
fhirEngine.search<Patient> {
filter(Resource.RES_ID, { value = of(patientId) })

private suspend fun getPatientObservations(): List<PatientListViewModel.ObservationItem> {
val observations: MutableList<PatientListViewModel.ObservationItem> = mutableListOf()
fhirEngine
.search<Observation> { filter(Observation.SUBJECT, { value = "Patient/$patientId" }) }
.take(MAX_RESOURCE_COUNT)
.map { createObservationItem(it, getApplication<Application>().resources) }
.let { observations.addAll(it) }
return observations
}
revInclude<RiskAssessment>(RiskAssessment.SUBJECT)
revInclude<Observation>(Observation.SUBJECT)
revInclude<Condition>(Condition.SUBJECT)
}
val data = mutableListOf<PatientDetailData>()

private suspend fun getPatientConditions(): List<PatientListViewModel.ConditionItem> {
val conditions: MutableList<PatientListViewModel.ConditionItem> = mutableListOf()
fhirEngine
.search<Condition> { filter(Condition.SUBJECT, { value = "Patient/$patientId" }) }
.take(MAX_RESOURCE_COUNT)
.map { createConditionItem(it, getApplication<Application>().resources) }
.let { conditions.addAll(it) }
return conditions
}
searchResult.first().let {
data.addPatientDetailData(
it.resource,
getRiskItem(
it.revIncluded?.get(ResourceType.RiskAssessment to RiskAssessment.SUBJECT.paramName)
as List<RiskAssessment>?
)
)

private suspend fun getPatientDetailDataModel(): List<PatientDetailData> {
val data = mutableListOf<PatientDetailData>()
val patient = getPatient()
patient.riskItem = getPatientRiskAssessment()
it.revIncluded?.get(ResourceType.Observation to Observation.SUBJECT.paramName)?.let {
data.addObservationsData(it as List<Observation>)
}
it.revIncluded?.get(ResourceType.Condition to Condition.SUBJECT.paramName)?.let {
data.addConditionsData(it as List<Condition>)
}
}
return data
}

val observations = getPatientObservations()
val conditions = getPatientConditions()
private fun getRiskItem(riskAssessments: List<RiskAssessment>?) =
riskAssessments
?.filter { it.hasOccurrence() }
?.maxByOrNull { it.occurrenceDateTimeType.value }
.let {
RiskAssessmentItem(
getRiskAssessmentStatusColor(it),
getRiskAssessmentStatus(it),
getLastContactedDate(it),
getPatientDetailsCardColor(it)
)
}

patient.let { patientItem ->
data.add(PatientDetailOverview(patientItem, firstInGroup = true))
data.add(
PatientDetailProperty(
PatientProperty(getString(R.string.patient_property_mobile), patientItem.phone)
private fun MutableList<PatientDetailData>.addPatientDetailData(
patient: Patient,
riskAssessment: RiskAssessmentItem
) {
patient
.toPatientItem(0)
.apply { riskItem = riskAssessment }
.let { patientItem ->
add(PatientDetailOverview(patientItem, firstInGroup = true))
add(
PatientDetailProperty(
PatientProperty(getString(R.string.patient_property_mobile), patientItem.phone)
)
)
)
data.add(
PatientDetailProperty(
PatientProperty(getString(R.string.patient_property_id), patientItem.resourceId)
add(
PatientDetailProperty(
PatientProperty(getString(R.string.patient_property_id), patientItem.resourceId)
)
)
)
data.add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_address),
"${patientItem.city}, ${patientItem.country} "
add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_address),
"${patientItem.city}, ${patientItem.country} "
)
)
)
)
data.add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_dob),
patientItem.dob?.localizedString ?: ""
add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_dob),
patientItem.dob?.localizedString ?: ""
)
)
)
)
data.add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_gender),
patientItem.gender.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
}
),
lastInGroup = true
add(
PatientDetailProperty(
PatientProperty(
getString(R.string.patient_property_gender),
patientItem.gender.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
}
),
lastInGroup = true
)
)
)
}
}
}

private fun MutableList<PatientDetailData>.addObservationsData(observations: List<Observation>) {
if (observations.isNotEmpty()) {
data.add(PatientDetailHeader(getString(R.string.header_observation)))
add(PatientDetailHeader(getString(R.string.header_observation)))

val observationDataModel =
observations.mapIndexed { index, observationItem ->
observations
.take(MAX_RESOURCE_COUNT)
.map { createObservationItem(it, getApplication<Application>().resources) }
.mapIndexed { index, observationItem ->
PatientDetailObservation(
observationItem,
firstInGroup = index == 0,
lastInGroup = index == observations.size - 1
)
}
data.addAll(observationDataModel)
.let { addAll(it) }
}
}

private fun MutableList<PatientDetailData>.addConditionsData(conditions: List<Condition>) {
if (conditions.isNotEmpty()) {
data.add(PatientDetailHeader(getString(R.string.header_conditions)))
val conditionDataModel =
conditions.mapIndexed { index, conditionItem ->
add(PatientDetailHeader(getString(R.string.header_conditions)))
conditions
.take(MAX_RESOURCE_COUNT)
.map { createConditionItem(it, getApplication<Application>().resources) }
.mapIndexed { index, conditionItem ->
PatientDetailCondition(
conditionItem,
firstInGroup = index == 0,
lastInGroup = index == conditions.size - 1
)
}
data.addAll(conditionDataModel)
.let { addAll(it) }
}

return data
}

private val LocalDate.localizedString: String
Expand All @@ -176,21 +201,6 @@ class PatientDetailsViewModel(

private fun getString(resId: Int) = getApplication<Application>().resources.getString(resId)

private suspend fun getPatientRiskAssessment(): RiskAssessmentItem {
val riskAssessment =
fhirEngine
.search<RiskAssessment> { filter(RiskAssessment.SUBJECT, { value = "Patient/$patientId" }) }
.filter { it.hasOccurrence() }
.sortedByDescending { it.occurrenceDateTimeType.value }
.firstOrNull()
return RiskAssessmentItem(
getRiskAssessmentStatusColor(riskAssessment),
getRiskAssessmentStatus(riskAssessment),
getLastContactedDate(riskAssessment),
getPatientDetailsCardColor(riskAssessment)
)
}

private fun getRiskAssessmentStatusColor(riskAssessment: RiskAssessment?): Int {
riskAssessment?.let {
return when (it.prediction.first().qualitativeRisk.coding.first().code) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.Search
import com.google.android.fhir.search.StringFilterModifier
import com.google.android.fhir.search.count
import com.google.android.fhir.search.search
Expand Down Expand Up @@ -82,7 +81,6 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
}
)
}
filterCity(this)
}
}

Expand All @@ -99,12 +97,11 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
}
)
}
filterCity(this)
sort(Patient.GIVEN, Order.ASCENDING)
count = 100
from = 0
}
.mapIndexed { index, fhirPatient -> fhirPatient.toPatientItem(index + 1) }
.mapIndexed { index, fhirPatient -> fhirPatient.resource.toPatientItem(index + 1) }
.let { patients.addAll(it) }

val risks = getRiskAssessments()
Expand All @@ -116,19 +113,15 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
return patients
}

private fun filterCity(search: Search) {
search.filter(Patient.ADDRESS_CITY, { value = "NAIROBI" })
}

private suspend fun getRiskAssessments(): Map<String, RiskAssessment?> {
return fhirEngine
.search<RiskAssessment> {}
.groupBy { it.subject.reference }
.groupBy { it.resource.subject.reference }
.mapValues { entry ->
entry.value
.filter { it.hasOccurrence() }
.sortedByDescending { it.occurrenceDateTimeType.value }
.firstOrNull()
.filter { it.resource.hasOccurrence() }
.maxByOrNull { it.resource.occurrenceDateTimeType.value }
?.resource
}
}

Expand Down
Loading
Loading