Skip to content

Commit

Permalink
Really big update (#4)
Browse files Browse the repository at this point in the history
* update

* Update FormService.kt

* update services

* Update local changes
  • Loading branch information
sevenreup authored Nov 12, 2024
1 parent 36d195c commit 61ea8e7
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 43 deletions.
12 changes: 10 additions & 2 deletions core/src/main/kotlin/com/google/android/fhir/LocalChange.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ data class LocalChange(
* [LocalChange] class instance is created.
*/
var token: LocalChangeToken,
val isPatch: Boolean = false,
) {
enum class Type(val value: Int) {
INSERT(1), // create a new resource. payload is the entire resource json.
Expand All @@ -38,9 +39,16 @@ data class LocalChange(
}
}

fun createPatchRequest(iParser: IParser, resource: Resource? = null): BundleEntryComponent {
fun createPatchRequest(
iParser: IParser,
resource: Resource? = null
): BundleEntryComponent {
return if (type == LocalChange.Type.UPDATE) {
createRequest(createPathRequest())
if (isPatch) {
createRequest(createPathRequest())
} else {
createRequest(iParser.parseResource(payload) as Resource)
}
} else if (resource != null && type == LocalChange.Type.INSERT) {
createRequest(resource)
} else {
Expand Down
100 changes: 66 additions & 34 deletions core/src/main/kotlin/org/dtree/fhir/core/fhir/PatchMaker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,39 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.github.fge.jsonpatch.diff.JsonDiff
import com.google.android.fhir.LocalChange
import com.google.android.fhir.LocalChangeToken
import com.google.android.fhir.sync.upload.patch.PatchOrdering.sccOrderByReferences
import org.dtree.fhir.core.utils.logicalId
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
import org.json.JSONArray
import org.hl7.fhir.r4.model.Resource
import org.json.JSONArray
import java.time.Instant
import java.util.UUID
import kotlin.random.Random

object PatchMaker {
private val resourceHelper = FhirResourceHelper()
fun createPatchedRequest(
iParser: IParser,
resourceMap: Map<String, Resource>,
resources: List<Resource>
): List<BundleEntryComponent> {
return resources.mapNotNull { resource ->
val oldResource = resourceMap[resource.logicalId]
?: return@mapNotNull LocalChange(
resourceType = resource.fhirType(),
resourceId = resource.logicalId,
versionId = resource.meta.versionId,
timestamp = Instant.now(),
type = LocalChange.Type.INSERT,
payload = iParser.encodeResourceToString(resource.apply {
id = logicalId
}),
token = LocalChangeToken(listOf())
).createPatchRequest(iParser, resource)
val changes = resources.mapNotNull { resource ->
val resourceID = resource.logicalId
var oldResource = resourceMap[resourceID]
if (oldResource == null) {
oldResource = resourceMap[resourceID.replace("#", "")]
}
if (oldResource == null) return@mapNotNull LocalChange(
resourceType = resource.fhirType(),
resourceId = resourceID,
versionId = resource.meta.versionId,
timestamp = Instant.now(),
type = LocalChange.Type.INSERT,
payload = iParser.encodeResourceToString(resource.apply {
id = logicalId
}),
token = LocalChangeToken(listOf(Random.nextLong())),
)

val jsonDiff = patch(iParser, oldResource.apply {
id = logicalId
Expand All @@ -42,46 +50,70 @@ object PatchMaker {
} else {
LocalChange(
resourceType = resource.fhirType(),
resourceId = resource.logicalId,
resourceId = resourceID,
versionId = resource.meta.versionId,
timestamp = Instant.now(),
type = LocalChange.Type.UPDATE,
payload = jsonDiff,
token = LocalChangeToken(listOf())
).createPatchRequest(iParser)
payload = jsonDiff.second,
token = LocalChangeToken(listOf(Random.nextLong())),
isPatch = jsonDiff.first
)
}
}
val refs = changes.flatMap {
val resource: Resource = if (it.isPatch) {
resourceMap[it.resourceId]!!
} else {
iParser.parseResource(it.payload) as Resource
}
resourceHelper.extractLocalChangeWithRefs(it, resource)
}
val ordered = refs.sccOrderByReferences()
val newLocalChanges = ordered.flatMap { it.patchMappings.map { it.localChange } }
return newLocalChanges.map { it.createPatchRequest(iParser) }
}

fun patch(iParser: IParser, oldResource: Resource, updatedResource: Resource): String? {
val jsonDiff: JSONArray = diff(iParser, oldResource, updatedResource)
private fun patch(parser: IParser, source: Resource, target: Resource): Pair<Boolean, String>? {
val objectMapper = ObjectMapper()
val sourceStr = objectMapper.readValue(parser.encodeResourceToString(source), JsonNode::class.java)
val targetStr = objectMapper.readValue(parser.encodeResourceToString(target), JsonNode::class.java)

val diff = JsonDiff.asJson(
sourceStr,
targetStr,
)

val response = getFilteredJSONArray(diff)
val jsonDiff = response.first
if (jsonDiff.length() == 0) {
println("New resource same as last one")
return null
}
return jsonDiff.toString()
}

private fun diff(parser: IParser, source: Resource, target: Resource): JSONArray {
val objectMapper = ObjectMapper()
return getFilteredJSONArray(
JsonDiff.asJson(
objectMapper.readValue(parser.encodeResourceToString(source), JsonNode::class.java),
objectMapper.readValue(parser.encodeResourceToString(target), JsonNode::class.java),
),
)
return Pair(true, jsonDiff.toString())
}

private fun getFilteredJSONArray(jsonDiff: JsonNode) =
with(JSONArray(jsonDiff.toString())) {
val ignorePaths = setOf("/meta", "/text")
private fun getFilteredJSONArray(jsonDiff: JsonNode): Pair<JSONArray, Boolean> {
var hasMeta = false
val ignorePaths = listOf("/meta", "/text")
val array = with(JSONArray(jsonDiff.toString())) {
return@with JSONArray(
(0..<length())
.map { optJSONObject(it) }
.filterNot { jsonObject ->
ignorePaths.any { jsonObject.optString("path").startsWith(it) } || jsonObject.optString("op")
val isRemove = jsonObject.optString("op")
.equals("remove")
if (jsonObject.optString("path").startsWith("/meta") && !isRemove) {
hasMeta = true
}
var paths = ignorePaths
if (hasMeta && !isRemove) {
paths = listOf("/text")
}
paths.any { jsonObject.optString("path").startsWith(it) } || isRemove
},
)
}
return Pair(array, hasMeta)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ fun Bundle.parsePatientResources(patientId: String): Pair<List<String>, PatientD
tasks = tasks.values.toMutableList(),
tracingTasks = tracingTasks,
carePlans = activeCarePlans,
currentCarePlan = currentCarePlan,
oldCarePlans = allCarePlans.filter { it.status != CarePlan.CarePlanStatus.ACTIVE && it.status != CarePlan.CarePlanStatus.ONHOLD }
.toMutableList(),
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import org.apache.commons.net.ntp.TimeStamp
import org.dtree.fhir.core.models.PatientData
import org.dtree.fhir.core.models.parsePatientResources
import org.dtree.fhir.core.utils.Logger
import org.dtree.fhir.core.utils.createFile
import org.dtree.fhir.core.utils.logicalId
import org.hl7.fhir.instance.model.api.IBaseBundle
import org.hl7.fhir.instance.model.api.IBaseResource
import org.hl7.fhir.r4.model.*
import java.util.concurrent.TimeUnit

import kotlin.io.path.Path

class FhirClient(private val dotenv: Dotenv, private val iParser: IParser) {
val fhirClient: IGenericClient
Expand Down Expand Up @@ -225,7 +227,8 @@ class FhirClient(private val dotenv: Dotenv, private val iParser: IParser) {
if (response is DataResponseState.Success) {
Logger.info("Uploaded successfully")
} else if (response is DataResponseState.Error) {
throw Exception(response.exception)
saveRemainingData(list.subList(start, list.size - 1))
throw FailedToUploadException(response.exception.toString())
}
}
}
Expand Down Expand Up @@ -270,6 +273,7 @@ class FhirClient(private val dotenv: Dotenv, private val iParser: IParser) {
DataResponseState.Success(true)
} catch (e: Exception) {
Logger.error("Failed to upload batch: ${e.message}")
Logger.info(iParser.encodeResourceToString(bundle))
DataResponseState.Error(e)
}
}
Expand All @@ -278,6 +282,19 @@ class FhirClient(private val dotenv: Dotenv, private val iParser: IParser) {
private fun exceptionFromResponse(response: Response): Exception {
return Exception("Status: ${response.code},message: ${response.message}, body: ${response.body?.string()} ")
}

private fun saveRemainingData(subList: List<Bundle.BundleEntryComponent>) {
dotenv["REPORT_DIR"]?.apply {
val time = TimeStamp.getCurrentTime()
val bundle = Bundle()
subList.forEach {
bundle.entry.add(it)
}
iParser.encodeResourceToString(bundle)
.createFile(Path(this).resolve("batch-upload-${time.time}.json").toString())
}

}
}

fun <T : IBaseResource> IQuery<Bundle>.paginateExecute(client: FhirClient): List<T> {
Expand All @@ -292,4 +309,6 @@ fun <T : IBaseResource> IQuery<Bundle>.paginateExecute(client: FhirClient): List
}

return list.toList() as List<T>
}
}

class FailedToUploadException(message: String) : Exception(message) {}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.dtree.fhir.server.controller

import org.dtree.fhir.server.plugins.tasks.ChangeAppointmentData
import org.dtree.fhir.server.plugins.tasks.ChangeStatusType
import org.dtree.fhir.server.plugins.tasks.FinishVisitRequest
import org.dtree.fhir.server.plugins.tasks.TracingRemovalType
import org.dtree.fhir.server.services.form.FormService
import org.dtree.fhir.server.services.util.UtilService
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class TasksControllerImpl : TasksController, BaseController(), KoinComponent {
private val formService by inject<FormService>()
private val utilService by inject<UtilService>()

override fun finishVisits(finishVisitRequestList: List<FinishVisitRequest>) {
formService.finishVisit(finishVisitRequestList)
Expand All @@ -18,13 +21,23 @@ class TasksControllerImpl : TasksController, BaseController(), KoinComponent {
formService.changeAppointmentData(changeAppointmentDataList)
}

override suspend fun changeStatus(patients: List<String>, type: ChangeStatusType) {
formService.changeStatus(patients, type)
}

override suspend fun tracingEnteredInError(patients: List<String>, type: TracingRemovalType) {
formService.tracingEnteredInError(patients, type)
}

override suspend fun runUtil() {
utilService.runCli()
}
}

interface TasksController {
fun finishVisits(finishVisitRequestList: List<FinishVisitRequest>)
suspend fun changeAppointmentData(changeAppointmentDataList: List<ChangeAppointmentData>)
suspend fun tracingEnteredInError(patients: List<String>, type: TracingRemovalType)
suspend fun runUtil()
suspend fun changeStatus(patients: List<String>, type: ChangeStatusType)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,19 @@ data class TracingEnteredErrorData(
val data: List<String>,
)

data class ChangeStatusData(
val type: ChangeStatusType,
val data: List<String>,
)

enum class TracingRemovalType {
EnteredInError,
TransferredOut,
Deceased
}

enum class ChangeStatusType {
Discharged,
Deceased,
EnteredInError
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ class Tasks() {

@Resource("tracing-entered-error")
class TracingEnteredError(val parent: Fixes = Fixes())

@Resource("change-status")
class ChangeStatus(val parent: Fixes = Fixes())
}
@Resource("util")
class Utils(val parent: Tasks = Tasks()) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.dtree.fhir.server.plugins.tasks

import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.resources.*
import io.ktor.server.resources.post
import io.ktor.server.response.*
import io.ktor.server.routing.Route
Expand Down Expand Up @@ -30,4 +31,17 @@ fun Route.tasksModule() {

call.respond("Jeff")
}

post<Tasks.Fixes.ChangeStatus> {
val body = call.receive<ChangeStatusData>()

controller.changeStatus(body.data, body.type)

call.respond("Jeff")
}

get<Tasks.Utils> {
controller.runUtil()
call.respond("Jeff")
}
}
Loading

0 comments on commit 61ea8e7

Please sign in to comment.