mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-04 07:22:55 +00:00
refactor: add types for patient id and pseudonym
This commit is contained in:
@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.input
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import de.ukw.ccc.bwhc.dto.Consent
|
||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||
@ -36,6 +37,7 @@ class KafkaInputListener(
|
||||
|
||||
override fun onMessage(data: ConsumerRecord<String, String>) {
|
||||
val mtbFile = objectMapper.readValue(data.value(), MtbFile::class.java)
|
||||
val patientId = PatientId(mtbFile.patient.id)
|
||||
val firstRequestIdHeader = data.headers().headers("requestId")?.firstOrNull()
|
||||
val requestId = if (null != firstRequestIdHeader) {
|
||||
RequestId(String(firstRequestIdHeader.value()))
|
||||
@ -53,9 +55,9 @@ class KafkaInputListener(
|
||||
} else {
|
||||
logger.debug("Accepted MTB File and process deletion")
|
||||
if (requestId.isBlank()) {
|
||||
requestProcessor.processDeletion(mtbFile.patient.id)
|
||||
requestProcessor.processDeletion(patientId)
|
||||
} else {
|
||||
requestProcessor.processDeletion(mtbFile.patient.id, requestId)
|
||||
requestProcessor.processDeletion(patientId, requestId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.input
|
||||
|
||||
import de.ukw.ccc.bwhc.dto.Consent
|
||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.ResponseEntity
|
||||
@ -46,7 +47,8 @@ class MtbFileRestController(
|
||||
requestProcessor.processMtbFile(mtbFile)
|
||||
} else {
|
||||
logger.debug("Accepted MTB File and process deletion")
|
||||
requestProcessor.processDeletion(mtbFile.patient.id)
|
||||
val patientId = PatientId(mtbFile.patient.id)
|
||||
requestProcessor.processDeletion(patientId)
|
||||
}
|
||||
return ResponseEntity.accepted().build()
|
||||
}
|
||||
@ -54,7 +56,7 @@ class MtbFileRestController(
|
||||
@DeleteMapping(path = ["{patientId}"])
|
||||
fun deleteData(@PathVariable patientId: String): ResponseEntity<Void> {
|
||||
logger.debug("Accepted patient ID to process deletion")
|
||||
requestProcessor.processDeletion(patientId)
|
||||
requestProcessor.processDeletion(PatientId(patientId))
|
||||
return ResponseEntity.accepted().build()
|
||||
}
|
||||
|
||||
|
@ -19,9 +19,7 @@
|
||||
|
||||
package dev.dnpm.etl.processor.monitoring
|
||||
|
||||
import dev.dnpm.etl.processor.Fingerprint
|
||||
import dev.dnpm.etl.processor.randomRequestId
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.*
|
||||
import org.springframework.data.annotation.Id
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
@ -38,8 +36,8 @@ import java.util.*
|
||||
data class Request(
|
||||
@Id val id: Long? = null,
|
||||
val uuid: RequestId = randomRequestId(),
|
||||
val patientId: String,
|
||||
val pid: String,
|
||||
val patientId: PatientPseudonym,
|
||||
val pid: PatientId,
|
||||
@Column("fingerprint")
|
||||
val fingerprint: Fingerprint,
|
||||
val type: RequestType,
|
||||
@ -49,8 +47,8 @@ data class Request(
|
||||
) {
|
||||
constructor(
|
||||
uuid: RequestId,
|
||||
patientId: String,
|
||||
pid: String,
|
||||
patientId: PatientPseudonym,
|
||||
pid: PatientId,
|
||||
fingerprint: Fingerprint,
|
||||
type: RequestType,
|
||||
status: RequestStatus
|
||||
@ -59,8 +57,8 @@ data class Request(
|
||||
|
||||
constructor(
|
||||
uuid: RequestId,
|
||||
patientId: String,
|
||||
pid: String,
|
||||
patientId: PatientPseudonym,
|
||||
pid: PatientId,
|
||||
fingerprint: Fingerprint,
|
||||
type: RequestType,
|
||||
status: RequestStatus,
|
||||
@ -83,11 +81,11 @@ data class CountedState(
|
||||
|
||||
interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRepository<Request, Long> {
|
||||
|
||||
fun findAllByPatientIdOrderByProcessedAtDesc(patientId: String): List<Request>
|
||||
fun findAllByPatientIdOrderByProcessedAtDesc(patientId: PatientPseudonym): List<Request>
|
||||
|
||||
fun findByUuidEquals(uuid: RequestId): Optional<Request>
|
||||
|
||||
fun findRequestByPatientId(patientId: String, pageable: Pageable): Page<Request>
|
||||
fun findRequestByPatientId(patientId: PatientPseudonym, pageable: Pageable): Page<Request>
|
||||
|
||||
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;")
|
||||
fun countStates(): List<CountedState>
|
||||
|
@ -63,7 +63,7 @@ class KafkaMtbFileSender(
|
||||
val dummyMtbFile = MtbFile.builder()
|
||||
.withConsent(
|
||||
Consent.builder()
|
||||
.withPatient(request.patientId)
|
||||
.withPatient(request.patientId.value)
|
||||
.withStatus(Consent.Status.REJECTED)
|
||||
.build()
|
||||
)
|
||||
@ -99,7 +99,7 @@ class KafkaMtbFileSender(
|
||||
}
|
||||
|
||||
private fun key(request: MtbFileSender.DeleteRequest): String {
|
||||
return "{\"pid\": \"${request.patientId}\"}"
|
||||
return "{\"pid\": \"${request.patientId.value}\"}"
|
||||
}
|
||||
|
||||
data class Data(val requestId: RequestId, val content: MtbFile)
|
||||
|
@ -20,6 +20,7 @@
|
||||
package dev.dnpm.etl.processor.output
|
||||
|
||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||
import dev.dnpm.etl.processor.PatientPseudonym
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.monitoring.RequestStatus
|
||||
import org.springframework.http.HttpStatusCode
|
||||
@ -35,7 +36,7 @@ interface MtbFileSender {
|
||||
|
||||
data class MtbFileRequest(val requestId: RequestId, val mtbFile: MtbFile)
|
||||
|
||||
data class DeleteRequest(val requestId: RequestId, val patientId: String)
|
||||
data class DeleteRequest(val requestId: RequestId, val patientId: PatientPseudonym)
|
||||
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package dev.dnpm.etl.processor.pseudonym
|
||||
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import dev.dnpm.etl.processor.PatientPseudonym
|
||||
import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties
|
||||
|
||||
class PseudonymizeService(
|
||||
@ -26,10 +28,10 @@ class PseudonymizeService(
|
||||
private val configProperties: PseudonymizeConfigProperties
|
||||
) {
|
||||
|
||||
fun patientPseudonym(patientId: String): String {
|
||||
fun patientPseudonym(patientId: PatientId): PatientPseudonym {
|
||||
return when (generator) {
|
||||
is GpasPseudonymGenerator -> generator.generate(patientId)
|
||||
else -> "${configProperties.prefix}_${generator.generate(patientId)}"
|
||||
is GpasPseudonymGenerator -> PatientPseudonym(generator.generate(patientId.value))
|
||||
else -> PatientPseudonym("${configProperties.prefix}_${generator.generate(patientId.value)}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
package dev.dnpm.etl.processor.pseudonym
|
||||
|
||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||
import dev.dnpm.etl.processor.PatientId
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
|
||||
/** Replaces patient ID with generated patient pseudonym
|
||||
@ -29,7 +30,7 @@ import org.apache.commons.codec.digest.DigestUtils
|
||||
* @return The MTB file containing patient pseudonymes
|
||||
*/
|
||||
infix fun MtbFile.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
|
||||
val patientPseudonym = pseudonymizeService.patientPseudonym(this.patient.id)
|
||||
val patientPseudonym = pseudonymizeService.patientPseudonym(PatientId(this.patient.id)).value
|
||||
|
||||
this.episode?.patient = patientPseudonym
|
||||
this.carePlans?.forEach { it.patient = patientPseudonym }
|
||||
|
@ -21,9 +21,7 @@ package dev.dnpm.etl.processor.services
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import de.ukw.ccc.bwhc.dto.MtbFile
|
||||
import dev.dnpm.etl.processor.Fingerprint
|
||||
import dev.dnpm.etl.processor.randomRequestId
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.*
|
||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||
import dev.dnpm.etl.processor.monitoring.Report
|
||||
import dev.dnpm.etl.processor.monitoring.Request
|
||||
@ -56,17 +54,19 @@ class RequestProcessor(
|
||||
}
|
||||
|
||||
fun processMtbFile(mtbFile: MtbFile, requestId: RequestId) {
|
||||
val pid = mtbFile.patient.id
|
||||
val pid = PatientId(mtbFile.patient.id)
|
||||
|
||||
mtbFile pseudonymizeWith pseudonymizeService
|
||||
mtbFile anonymizeContentWith pseudonymizeService
|
||||
|
||||
val request = MtbFileSender.MtbFileRequest(requestId, transformationService.transform(mtbFile))
|
||||
|
||||
val patientPseudonym = PatientPseudonym(request.mtbFile.patient.id)
|
||||
|
||||
requestService.save(
|
||||
Request(
|
||||
requestId,
|
||||
request.mtbFile.patient.id,
|
||||
patientPseudonym,
|
||||
pid,
|
||||
fingerprint(request.mtbFile),
|
||||
RequestType.MTB_FILE,
|
||||
@ -101,20 +101,22 @@ class RequestProcessor(
|
||||
}
|
||||
|
||||
private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean {
|
||||
val patientPseudonym = PatientPseudonym(pseudonymizedMtbFile.patient.id)
|
||||
|
||||
val lastMtbFileRequestForPatient =
|
||||
requestService.lastMtbFileRequestForPatientPseudonym(pseudonymizedMtbFile.patient.id)
|
||||
val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(pseudonymizedMtbFile.patient.id)
|
||||
requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
|
||||
val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
|
||||
|
||||
return null != lastMtbFileRequestForPatient
|
||||
&& !isLastRequestDeletion
|
||||
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFile)
|
||||
}
|
||||
|
||||
fun processDeletion(patientId: String) {
|
||||
fun processDeletion(patientId: PatientId) {
|
||||
processDeletion(patientId, randomRequestId())
|
||||
}
|
||||
|
||||
fun processDeletion(patientId: String, requestId: RequestId) {
|
||||
fun processDeletion(patientId: PatientId, requestId: RequestId) {
|
||||
try {
|
||||
val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
|
||||
|
||||
@ -123,7 +125,7 @@ class RequestProcessor(
|
||||
requestId,
|
||||
patientPseudonym,
|
||||
patientId,
|
||||
fingerprint(patientPseudonym),
|
||||
fingerprint(patientPseudonym.value),
|
||||
RequestType.DELETE,
|
||||
RequestStatus.UNKNOWN
|
||||
)
|
||||
@ -147,7 +149,7 @@ class RequestProcessor(
|
||||
requestService.save(
|
||||
Request(
|
||||
uuid = requestId,
|
||||
patientId = "???",
|
||||
patientId = emptyPatientPseudonym(),
|
||||
pid = patientId,
|
||||
fingerprint = Fingerprint.empty(),
|
||||
status = RequestStatus.ERROR,
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
package dev.dnpm.etl.processor.services
|
||||
|
||||
import dev.dnpm.etl.processor.PatientPseudonym
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.monitoring.*
|
||||
import org.springframework.data.domain.Page
|
||||
@ -40,15 +41,15 @@ class RequestService(
|
||||
fun findByUuid(uuid: RequestId): Optional<Request> =
|
||||
requestRepository.findByUuidEquals(uuid)
|
||||
|
||||
fun findRequestByPatientId(patientId: String, pageable: Pageable): Page<Request> = requestRepository.findRequestByPatientId(patientId, pageable)
|
||||
fun findRequestByPatientId(patientId: PatientPseudonym, pageable: Pageable): Page<Request> = requestRepository.findRequestByPatientId(patientId, pageable)
|
||||
|
||||
fun allRequestsByPatientPseudonym(patientPseudonym: String) = requestRepository
|
||||
fun allRequestsByPatientPseudonym(patientPseudonym: PatientPseudonym) = requestRepository
|
||||
.findAllByPatientIdOrderByProcessedAtDesc(patientPseudonym)
|
||||
|
||||
fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: String) =
|
||||
fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: PatientPseudonym) =
|
||||
Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym))
|
||||
|
||||
fun isLastRequestWithKnownStatusDeletion(patientPseudonym: String) =
|
||||
fun isLastRequestWithKnownStatusDeletion(patientPseudonym: PatientPseudonym) =
|
||||
Companion.isLastRequestWithKnownStatusDeletion(allRequestsByPatientPseudonym(patientPseudonym))
|
||||
|
||||
fun countStates(): Iterable<CountedState> = requestRepository.countStates()
|
||||
|
@ -38,4 +38,12 @@ value class RequestId(val value: String) {
|
||||
|
||||
}
|
||||
|
||||
fun randomRequestId() = RequestId(UUID.randomUUID().toString())
|
||||
fun randomRequestId() = RequestId(UUID.randomUUID().toString())
|
||||
|
||||
@JvmInline
|
||||
value class PatientId(val value: String)
|
||||
|
||||
@JvmInline
|
||||
value class PatientPseudonym(val value: String)
|
||||
|
||||
fun emptyPatientPseudonym() = PatientPseudonym("")
|
@ -20,6 +20,7 @@
|
||||
package dev.dnpm.etl.processor.web
|
||||
|
||||
import dev.dnpm.etl.processor.NotFoundException
|
||||
import dev.dnpm.etl.processor.PatientPseudonym
|
||||
import dev.dnpm.etl.processor.RequestId
|
||||
import dev.dnpm.etl.processor.monitoring.ReportService
|
||||
import dev.dnpm.etl.processor.services.RequestService
|
||||
@ -56,7 +57,7 @@ class HomeController(
|
||||
@PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable,
|
||||
model: Model
|
||||
): String {
|
||||
val requests = requestService.findRequestByPatientId(patientId, pageable)
|
||||
val requests = requestService.findRequestByPatientId(PatientPseudonym(patientId), pageable)
|
||||
model.addAttribute("patientId", patientId)
|
||||
model.addAttribute("requests", requests)
|
||||
|
||||
|
Reference in New Issue
Block a user