Skip to main content

Share different parts of the same medical event with different actors

Overview​

In some cases, you may want to share different parts of the same Contact with different actors. Let's consider this example:

A doctor visit a patient, measure their blood pressure, their heart rate, and finally adds a diagnosis and a prescription for a pill.

Using the Cardinal SDK, this translates to create a Contact with 3 Services (one for the blood pressure, one for the ECG, one for the prescription), and a note for the diagnosis.

Then the doctor asks the patient if they want to participate in a research study. For the study, the patient has to share their ECG with the doctor responsible for the study.

This is not possible if only one Contact is created, as it is not possible to share different entities with different data owners. Instead, the data should be split into different Contacts. All of these contacts should have the same groupId, that marks them as belonging to the same "Logical Contact".

When retrieving multiple Contacts with the same group, you can decide to combine them after retrieval for an easier visualization.

Below, you will find a code example for this use case.

Prerequisites​

For this use case example, let's consider to doctors: the first that is performing the visit and the second that is responsible for the research study. They both have to log in and initialize the SDK.

import com.icure.cardinal.sdk.CardinalSdk
import com.icure.cardinal.sdk.auth.UsernamePassword
import com.icure.cardinal.sdk.options.AuthenticationMethod
import com.icure.cardinal.sdk.storage.impl.FileStorageFacade

val CARDINAL_URL = "https://api.icure.cloud"

val visitingDoctorUsername = <VISITING_DOCTOR_USERNAME>
val visitingDoctorPassword = <VISITING_DOCTOR_PASSWORD>

val researchDoctorUsername = <RESEARCH_DOCTOR_USERNAME>
val researchDoctorPassword = <RESEARCH_DOCTOR_PASSWORD>

val visitingDoctorSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(visitingDoctorUsername, visitingDoctorPassword)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
val researchDoctorSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(researchDoctorUsername, researchDoctorPassword)
),
baseStorage = FileStorageFacade("./scratch/storage")
)

Then, we assume that the patient is not registered yet in the system, so the doctor that is doing the examination creates it.

Notice that the doctor does not set up any delegation for the patient when creating it, meaning that the doctor responsible for the research study will not be able to access it.

import com.icure.cardinal.sdk.model.DecryptedPatient
import java.util.UUID

val patientToCreate = DecryptedPatient(
id = UUID.randomUUID().toString(),
firstName = "Rupert",
lastName = "Venables",
)
val patient = visitingDoctorSdk.patient.createPatient(
visitingDoctorSdk.patient.withEncryptionMetadata(patientToCreate)
)

Finally, the doctor performing the visit needs the id of the doctor responsible for the research study to share data with them. In a real case, this means searching for the doctor and getting the id but, for the sake of the example, we assume that this already happened before.

// In a real scenario, the first doctor would need to search for the other doctor.
// For the sake of the example, it will be in the following variable
val researchDoctorId = researchDoctorSdk.healthcareParty.getCurrentHealthcareParty().id

Dividing the result of the examination in multiple contacts​

Now, the doctor has to register the data acquired during the examination: the heart rate, the blood pressure, and the medication prescribed. The doctor will instantiate a Service for each of those.

import com.icure.cardinal.sdk.model.embed.DecryptedContent
import com.icure.cardinal.sdk.model.embed.DecryptedService
import com.icure.cardinal.sdk.model.embed.Measure
import com.icure.cardinal.sdk.model.embed.Medication
import com.icure.cardinal.sdk.model.embed.Substanceproduct
import com.icure.cardinal.sdk.model.embed.TimeSeries
import java.util.UUID

val bloodPressureService = DecryptedService(
id = UUID.randomUUID().toString(),
label = "Blood pressure",
valueDate = 20240920154600,
content = mapOf(
"en" to DecryptedContent(
measureValue = Measure(
value = Random.nextInt(80, 120).toDouble(),
unit = "mmHg"
)
)
)
)

val ecgSignal = List(10) { Random.nextInt(0, 100) / 100.0 }
val heartRateService = DecryptedService(
id = UUID.randomUUID().toString(),
label = "Heart rate",
valueDate = 20240920154600,
content = mapOf(
"en" to DecryptedContent(
timeSeries = TimeSeries(
samples = listOf(ecgSignal)
)
)
)
)

val medicationService = DecryptedService(
id = UUID.randomUUID().toString(),
label = "Prescription",
valueDate = 20240920154600,
content = mapOf(
"en" to DecryptedContent(
medicationValue = Medication(
substanceProduct = Substanceproduct(
deliveredname = "lisinopril"
),
instructionForPatient = "10mg before breakfast."
)
)
)
)

Then, the doctor will divide these Services into 2 Contacts: one containing the blood pressure, the prescription, and the diagnosis as a note, the other only containing the result of the ECG. The first contact will not be shared with the other doctor, while the second one will be shared with the other doctor with read permission.

Both Contacts will share the same groupId: this way, they can be recognized as part of a single examination.

import com.icure.cardinal.sdk.model.DecryptedContact
import com.icure.cardinal.sdk.model.embed.AccessLevel
import com.icure.cardinal.sdk.model.embed.Annotation
import java.util.UUID

// The id of the "Logical Contact"
val groupId = UUID.randomUUID().toString()

val contactForResearch = DecryptedContact(
id = UUID.randomUUID().toString(),
openingDate = 20240920154460,
groupId = groupId, // This indicates that the Contact is part of a "Logical Contact"
services = setOf(
heartRateService
)
)

val contact = DecryptedContact(
id = UUID.randomUUID().toString(),
closingDate = 20240920164460,
groupId = groupId, // This indicates that the Contact is part of a "Logical Contact"
notes = listOf(
Annotation(
id = UUID.randomUUID().toString(),
markdown = mapOf(
"en" to "The Patient has hypertension."
)
)

),
services = setOf(
bloodPressureService,
medicationService
)
)

visitingDoctorSdk.contact.createContact(
visitingDoctorSdk.contact.withEncryptionMetadata(contact, patient)
)

visitingDoctorSdk.contact.createContact(
visitingDoctorSdk.contact.withEncryptionMetadata(
contactForResearch,
patient,
delegates = mapOf(researchDoctorId to AccessLevel.Read)
)
)

Retrieving the data​

Now both doctors can retrieve the part of that medical examination they can access.

The doctor that performed the visit has full access to both Contacts and to the Patient, so it will retrieve all the Services based on the link with the Patient using a filter.

import com.icure.cardinal.sdk.filters.ServiceFilters

val allServicesIterator = visitingDoctorSdk.contact.filterServicesBy(
ServiceFilters.byPatientsForSelf(listOf(patient))
)

while (allServicesIterator.hasNext()) {
allServicesIterator.next(10).forEach { service ->
println(service)
}
}

On the other hand, the doctor responsible for the research study cannot access the Patient, but only the anonymous part of the Contact shared with them. Therefore, they will search based on the openingDate of the Contact using a filter. This will return only the Contact that they can access, the one with the ECG result.

import com.icure.cardinal.sdk.filters.ContactFilters

val researchContactIterator = researchDoctorSdk.contact.filterContactsBy(
ContactFilters.byOpeningDateForSelf(
startDate = 20240920000000
)
)

while (researchContactIterator.hasNext()) {
researchContactIterator.next(10).forEach { researchContact ->
researchContact.services.forEach { service ->
println(service)
}
}
}