Skip to content

Commit

Permalink
Add config Composition section to support custom search parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
LZRS committed Jan 21, 2025
1 parent 9ffbae4 commit 1db3ecb
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
package org.smartregister.fhircore.engine.configuration.app

import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.SearchParameter
import org.smartregister.fhircore.engine.sync.ResourceTag
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
Expand Down Expand Up @@ -73,70 +71,7 @@ interface ConfigService {
return tags
}

/**
* Provide a list of custom search parameters.
*
* @return list of predefined custom search parameters.
*/
fun provideCustomSearchParameters(): List<SearchParameter> {
val activeGroupSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/group-active"
addBase("Group")
name = ACTIVE_SEARCH_PARAM
code = ACTIVE_SEARCH_PARAM
type = Enumerations.SearchParamType.TOKEN
expression = "Group.active"
description = "Search the active field"
}

val flagStatusSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/flag-status"
addBase("Flag")
name = STATUS_SEARCH_PARAM
code = STATUS_SEARCH_PARAM
type = Enumerations.SearchParamType.TOKEN
expression = "Flag.status"
description = "Search the status field"
}

val medicationSortSearchParameter =
SearchParameter().apply {
url = MEDICATION_SORT_URL
addBase("Medication")
name = SORT_SEARCH_PARAM
code = SORT_SEARCH_PARAM
type = Enumerations.SearchParamType.NUMBER
expression = "Medication.extension.where(url = '$MEDICATION_SORT_URL').value"
description = "Search the sort field"
}

val patientSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/patient-search"
addBase("Patient")
name = SEARCH_PARAM
code = SEARCH_PARAM
type = Enumerations.SearchParamType.STRING
expression = "Patient.name.text | Patient.identifier.value"
description = "Search patients by name and identifier fields"
}

return listOf(
activeGroupSearchParameter,
flagStatusSearchParameter,
medicationSortSearchParameter,
patientSearchParameter,
)
}

companion object {
const val ACTIVE_SEARCH_PARAM = "active"
const val APP_VERSION = "AppVersion"
const val STATUS_SEARCH_PARAM = "status"
const val SORT_SEARCH_PARAM = "sort"
const val SEARCH_PARAM = "search"
const val MEDICATION_SORT_URL = "http://smartregister.org/SearchParameter/medication-sort"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2021-2024 Ona Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.smartregister.fhircore.engine.configuration.app

import ca.uhn.fhir.parser.IParser
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Composition
import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.SearchParameter
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.extension.extractType

class CustomSearchParameterService(
private val storageDir: File,
private val iParser: IParser,
private val dispatcherProvider: DispatcherProvider,
) {

fun getCustomSearchParameters(): List<SearchParameter> {
return predefinedCustomSearchParameters + readSavedSearchParameters()
}

private fun readSavedSearchParameters(): List<SearchParameter> {
val file = File(storageDir, CUSTOM_SEARCH_PARAMETER_FILE_NAME)
if (!file.exists()) {
return emptyList()
}

val searchParametersBundle =
FileInputStream(file).bufferedReader().use { iParser.parseResource(Bundle::class.java, it) }
return searchParametersBundle.entry
.filter { it.resource.resourceType == ResourceType.SearchParameter }
.mapNotNull { it.resource as? SearchParameter }
}

suspend fun saveBundle(bundle: Bundle) {
val file = File(storageDir, CUSTOM_SEARCH_PARAMETER_FILE_NAME)
withContext(dispatcherProvider.io()) {
FileOutputStream(file).use { it.write(iParser.encodeResourceToString(bundle).toByteArray()) }
}
}

/** List of predefined custom search parameters. */
private val predefinedCustomSearchParameters: List<SearchParameter>
get() {
val activeGroupSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/group-active"
addBase("Group")
name = ACTIVE_SEARCH_PARAM
code = ACTIVE_SEARCH_PARAM
type = Enumerations.SearchParamType.TOKEN
expression = "Group.active"
description = "Search the active field"
}

val flagStatusSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/flag-status"
addBase("Flag")
name = STATUS_SEARCH_PARAM
code = STATUS_SEARCH_PARAM
type = Enumerations.SearchParamType.TOKEN
expression = "Flag.status"
description = "Search the status field"
}

val medicationSortSearchParameter =
SearchParameter().apply {
url = MEDICATION_SORT_URL
addBase("Medication")
name = SORT_SEARCH_PARAM
code = SORT_SEARCH_PARAM
type = Enumerations.SearchParamType.NUMBER
expression = "Medication.extension.where(url = '$MEDICATION_SORT_URL').value"
description = "Search the sort field"
}

val patientSearchParameter =
SearchParameter().apply {
url = "http://smartregister.org/SearchParameter/patient-search"
addBase("Patient")
name = SEARCH_PARAM
code = SEARCH_PARAM
type = Enumerations.SearchParamType.STRING
expression = "Patient.name.text | Patient.identifier.value"
description = "Search patients by name and identifier fields"
}

return listOf(
activeGroupSearchParameter,
flagStatusSearchParameter,
medicationSortSearchParameter,
patientSearchParameter,
)
}

companion object {
private const val CUSTOM_SEARCH_PARAMETER_FILE_NAME = "customSearchParameters.json"

private const val ACTIVE_SEARCH_PARAM = "active"
private const val STATUS_SEARCH_PARAM = "status"
private const val SORT_SEARCH_PARAM = "sort"
private const val SEARCH_PARAM = "search"
private const val MEDICATION_SORT_URL =
"http://smartregister.org/SearchParameter/medication-sort"
}
}

fun isCompositionSectionSearchParameter(section: Composition.SectionComponent): Boolean =
section.code.coding.any {
it.system.lowercase() == SEARCH_PARAMETER_SECTION_SYSTEM_URL &&
it.code.lowercase() == SEARCH_PARAMETER_SECTION_CODE
}

fun isSearchParameterConfigReferenceValid(reference: Reference): Boolean =
reference.extractType() == ResourceType.Bundle

private const val SEARCH_PARAMETER_SECTION_SYSTEM_URL =
"http://smartregister.org/CodeSystem/composition-section-codes"
private const val SEARCH_PARAMETER_SECTION_CODE = "custom-search-parameter-bundle"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.smartregister.fhircore.engine.di

import android.content.Context
import ca.uhn.fhir.parser.IParser
import com.google.android.fhir.DatabaseErrorStrategy
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.FhirEngineConfiguration
Expand All @@ -32,10 +33,12 @@ import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import org.smartregister.fhircore.engine.BuildConfig
import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.configuration.app.CustomSearchParameterService
import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator
import org.smartregister.fhircore.engine.di.NetworkModule.Companion.AUTHORIZATION
import org.smartregister.fhircore.engine.di.NetworkModule.Companion.COOKIE
import org.smartregister.fhircore.engine.di.NetworkModule.Companion.TIMEOUT_DURATION
import org.smartregister.fhircore.engine.util.DispatcherProvider
import timber.log.Timber

/**
Expand All @@ -45,12 +48,23 @@ import timber.log.Timber
@Module
class FhirEngineModule {

@Singleton
@Provides
fun provideCustomSearchParameterService(
@ApplicationContext context: Context,
parser: IParser,
dispatcherProvider: DispatcherProvider,
): CustomSearchParameterService {
return CustomSearchParameterService(context.filesDir, parser, dispatcherProvider)
}

@Singleton
@Provides
fun provideFhirEngine(
@ApplicationContext context: Context,
tokenAuthenticator: TokenAuthenticator,
configService: ConfigService,
customSearchParameterService: CustomSearchParameterService,
): FhirEngine {
FhirEngineProvider.init(
FhirEngineConfiguration(
Expand All @@ -76,7 +90,7 @@ class FhirEngineModule {
Timber.tag(QUEST_OKHTTP_CLIENT_TAG).d(it)
},
),
customSearchParameters = configService.provideCustomSearchParameters(),
customSearchParameters = customSearchParameterService.getCustomSearchParameters(),
),
)
return FhirEngineProvider.getInstance(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ fun Reference.extractType(): ResourceType? =
if (this.reference.isNullOrEmpty()) {
null
} else {
this.reference.substringBefore("/" + this.extractId()).substringAfterLast("/").let {
ResourceType.fromCode(it)
}
(this.type ?: this.reference.substringBefore("/" + this.extractId()).substringAfterLast("/"))
.let { ResourceType.fromCode(it) }
}

fun String.asReference(resourceType: ResourceType): Reference {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@ fun Composition.retrieveCompositionSections(): List<Composition.SectionComponent
return sections
}

fun Composition.SectionComponent.sectionDataReference(): Iterable<Reference> {
return if (hasFocus() && focus.hasReferenceElement()) entry + focus else entry
}

fun String.resourceClassType(): Class<out Resource> =
FhirContext.forR4().getResourceDefinition(this).implementingClass as Class<out Resource>

Expand Down
Loading

0 comments on commit 1db3ecb

Please sign in to comment.