Implement the Publisher
Publisher Application Overview​
The Publisher is any application that a patient can use to create medical data. In this example, it is a CLI that creates samples simulating a device that measures the sugar level in blood.
Since patient users cannot be created directly through the cockpit and to keep this tutorial self-contained, the application will first log in as a healthcare party to create the patient user. This newly created patient user will then be used for the rest of the example.
Create the Patient User​
- Kotlin
- Python
- Typescript
- Dart
First, the user will be prompted to insert the credentials of a healthcare party created through the cockpit. An SDK instance will be initialized for that healthcare party.
private const val CARDINAL_URL = "https://api.icure.cloud"
print("Login: ")
val username = readln().trim()
print("Password: ")
val password = readln().trim()
val sdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(username, password)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
First, the user will be prompted to insert the credentials of a healthcare party created through the cockpit. An SDK instance will be initialized for that healthcare party.
CARDINAL_URL = "https://api.icure.cloud"
username = input("Username: ")
password = input("Password: ")
sdk = CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(username, password),
storage_facade=FileSystemStorage("../scratch/storage")
)
First, the user will be prompted to insert the credentials of a healthcare party created through the cockpit. An SDK instance will be initialized for that healthcare party.
const CARDINAL_URL = "https://api.icure.cloud"
const username = await readLn("Login: ")
const password = await readLn("Password: ")
sdk = await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(username, password),
StorageFacade.usingFileSystem("../scratch/storage")
)
In the following example, the function initializes an SDK with a username and a password retrieved from the app UI:
const cardinalUrl = "https://api.icure.cloud";
Future<CardinalSdk> createSdk(String username, String password) async {
final sdk = await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(username, password)),
StorageOptions.PlatformDefault
);
return sdk;
}
Npw, the healthcare party user can create a Patient entity.
- Kotlin
- Python
- Typescript
- Dart
val newPatient = DecryptedPatient(
id = UUID.randomUUID().toString(),
firstName = "Edmond",
lastName = "Dantes",
)
val patientWithMetadata = sdk.patient.withEncryptionMetadata(newPatient)
val createdPatient = sdk.patient.createPatient(patientWithMetadata)
new_patient = DecryptedPatient(
id=str(uuid.uuid4()),
first_name="Edmond",
last_name="Dantes",
)
patient_with_metadata = sdk.patient.with_encryption_metadata_blocking(new_patient)
created_patient = sdk.patient.create_patient_blocking(patient_with_metadata)
const newPatient = new DecryptedPatient({
id: uuid(),
firstName: "Edmond",
lastName: "Dantes",
})
const patientWithMetadata = await sdk.patient.withEncryptionMetadata(newPatient)
const createdPatient = await sdk.patient.createPatient(patientWithMetadata)
final newPatient = DecryptedPatient(
generateUuid(),
firstName: "Edmond",
lastName: "Dantes",
);
final patientWithMetadata = await sdk.patient.withEncryptionMetadata(newPatient);
final createdPatient = await sdk.patient.createPatient(patientWithMetadata);
Then, it creates a user associated to the patient. The healthcare party also creates a temporary token that will allow the new user to log in.
- Kotlin
- Python
- Typescript
- Dart
val login = "edmond.dantes.${UUID.randomUUID().toString().substring(0, 6)}@icure.com"
val patientUser = User(
id = UUID.randomUUID().toString(),
patientId = createdPatient.id,
login = login,
email = login
)
val createdUser = sdk.user.createUser(patientUser)
val loginToken = sdk.user.getToken(createdUser.id, "login")
login = f"edmond.dantes.{str(uuid.uuid4())[0:6]}@icure.com"
patient_user = User(
id=str(uuid.uuid4()),
patient_id=created_patient.id,
login=login,
email=login
)
created_user = sdk.user.create_user_blocking(patient_user)
login_token = sdk.user.get_token_blocking(created_user.id, "login")
const login = `edmond.dantes.${uuid().substring(0, 6)}@icure.com`
const patientUser = new User({
id: uuid(),
patientId: createdPatient.id,
login: login,
email: login
})
const createdUser = await sdk.user.createUser(patientUser)
const loginToken = await sdk.user.getToken(createdUser.id, "login")
final login = "edmond.dantes.${generateUuid().substring(0, 6)}@icure.com";
final patientUser = User(
generateUuid(),
patientId: createdPatient.id,
login: login,
email: login
);
final createdUser = await sdk.user.createUser(patientUser);
final loginToken = await sdk.user.getToken(createdUser.id, "login");
Now, the patient user can log in and instantiate a new sdk. By doing so, they will also initialize their cryptographic key.
- Kotlin
- Python
- Typescript
- Dart
CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
),
baseStorage = FileStorageFacade("./scratch/storage")
)
CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(login, login_token),
storage_facade=FileSystemStorage("../scratch/storage")
)
await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(login, loginToken),
StorageFacade.usingFileSystem("../scratch/storage")
)
await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(username, password)),
StorageOptions.PlatformDefault
);
However, the Patient (entity) is an encrypted entity. The healthcare party user could not share it with the Patient (user), because their key were not initialized yet. Now that they are, the healthcare party can share the patient with itself.
A user that acts as a patient must be able to access their own patient entity to create, read, and share data through Cardinal.
- Kotlin
- Python
- Typescript
- Dart
val patient = sdk.patient.shareWith(
delegateId = createdPatient.id,
patient = createdPatient,
options = PatientShareOptions(
shareSecretIds = SecretIdShareOptions.AllAvailable(true),
shareEncryptionKey = ShareMetadataBehaviour.IfAvailable,
requestedPermissions = RequestedPermission.MaxWrite
)
)
patient = sdk.patient.share_with_blocking(
delegate_id=created_patient.id,
patient=created_patient,
options=PatientShareOptions(
share_secret_ids=SecretIdShareOptionsAllAvailable(True),
share_encryption_key=ShareMetadataBehaviour.IfAvailable,
requested_permissions=RequestedPermission.MaxWrite
)
)
const patient = await sdk.patient.shareWith(
createdPatient.id,
createdPatient,
{
options: new PatientShareOptions({
shareSecretIds: new SecretIdShareOptions.AllAvailable({ requireAtLeastOne: true }),
shareEncryptionKey: ShareMetadataBehaviour.IfAvailable,
requestedPermissions: RequestedPermission.MaxWrite
})
}
)
await sdk.patient.shareWith(
createdPatient.id,
createdPatient,
options: PatientShareOptions(
shareSecretIds: SecretIdShareOptionsAllAvailable(true),
shareEncryptionKey: ShareMetadataBehaviour.ifAvailable,
requestedPermissions: RequestedPermission.maxWrite
)
);
Now that the patient has the permission to access their own information, it can log in and create data.
- Kotlin
- Python
- Typescript
- Dart
val patientSdk = CardinalSdk.initialize(
applicationId = null,
baseUrl = CARDINAL_URL,
authenticationMethod = AuthenticationMethod.UsingCredentials(
UsernamePassword(login, loginToken)
)
patient_sdk = CardinalSdk(
application_id=None,
baseurl=CARDINAL_URL,
authentication_method=UsernamePassword(login, login_token),
storage_facade=FileSystemStorage("../scratch/storage")
)
const patientSdk = await CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(login, loginToken),
StorageFacade.usingFileSystem("../scratch/storage")
)
const patientSdk = await CardinalSdk.initialize(
null,
cardinalUrl,
AuthenticationMethod.UsingCredentials(Credentials.UsernamePassword(username, password)),
StorageOptions.PlatformDefault
);
This registration flow makes sense only in the context of this example, to make it self-contained. To learn how to register a Patient in a real context, check this how to.
Create Medical Data​
The patient user is now completely initialized and can start creating data. The Publisher simulates a medical
device capable of measuring glycemia. This measurement is stored through the Cardinal SDK as a Service
inside of a
Contact
, as explained in the previous tutorial.
- Kotlin
- Python
- Typescript
- Dart
val glycemiaValue = Random.nextInt(60, 160).toDouble()
val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
val contact = DecryptedContact(
id = UUID.randomUUID().toString(),
openingDate = LocalDateTime.now().format(formatter).toLong(),
services = setOf(
DecryptedService(
id = UUID.randomUUID().toString(),
content = mapOf(
"en" to DecryptedContent(
measureValue = Measure(
value = glycemiaValue,
unitCodes = setOf(
CodeStub(
id = "UCUM|mmol/L|1",
type="UCUM",
code="mmol/L",
version="1"
)
)
)
)
),
tags = setOf(
CodeStub(
id="LOINC|2339-0|1",
type="LOINC",
code="2339-0",
version="1"
),
CodeStub(
id="CARDINAL|TO_BE_ANALYZED|1",
type="CARDINAL",
code="TO_BE_ANALYZED",
version="1"
)
)
)
)
)
glycemia_value = float(random.randint(60, 160))
contact = DecryptedContact(
id=str(uuid.uuid4()),
opening_date=int(datetime.now().strftime("%Y%m%d%H%M%S")),
services=[
DecryptedService(
id=str(uuid.uuid4()),
content={
"en": DecryptedContent(
measure_value=Measure(
value=glycemia_value,
unit_codes=[
CodeStub(
id="UCUM|mmol/L|1",
type="UCUM",
code="mmol/L",
version="1"
)
]
)
)
},
tags=[
CodeStub(
id="LOINC|2339-0|1",
type="LOINC",
code="2339-0",
version="1"
),
CodeStub(
id="CARDINAL|TO_BE_ANALYZED|1",
type="CARDINAL",
code="TO_BE_ANALYZED",
version="1"
)
]
)
]
)
const glycemiaValue = random(60, 160)
const contact = new DecryptedContact({
id: uuid(),
openingDate: currentFuzzyDate(),
services: [
new DecryptedService({
id: uuid(),
content: {
"en": new DecryptedContent({
measureValue: new Measure({
value: glycemiaValue,
unitCodes: [
new CodeStub({
id: "UCUM|mmol/L|1",
type: "UCUM",
code: "mmol/L",
version: "1"
})
]
})
})
},
tags: [
new CodeStub({
id: "LOINC|2339-0|1",
type: "LOINC",
code: "2339-0",
version: "1"
}),
new CodeStub({
id: "CARDINAL|TO_BE_ANALYZED|1",
type: "CARDINAL",
code: "TO_BE_ANALYZED",
version: "1"
})
]
})
]
})
final glycemiaValue = (100 + Random().nextInt(60)).toDouble();
final contact = DecryptedContact(
generateUuid(),
openingDate: currentDateAsYYYYMMddHHmmSS(),
services: {
DecryptedService(
generateUuid(),
content: {
"en": DecryptedContent(
measureValue: Measure(
value: glycemiaValue,
unitCodes: {
CodeStub(
id: "UCUM|mmol/L|1",
type: "UCUM",
code: "mmol/L",
version: "1"
)
}
)
)
},
tags: {
CodeStub(
id: "LOINC|2339-0|1",
type: "LOINC",
code: "2339-0",
version: "1"
),
CodeStub(
id: "CARDINAL|TO_BE_ANALYZED|1",
type: "CARDINAL",
code: "TO_BE_ANALYZED",
version: "1"
)
}
)}
);
Three different codifications were used in the Service:
- One (
UCUM|mmol/L|1
) was used in theMeasure
to associate to the number the UCUM code for mmol/L, the standard unit for glycemia tests. - Another (
LOINC|2339-0|1
) was used to include the information that the service represents a glycemia test. For that, a LOINC code (2339-0) was used. - The last one (
CARDINAL|TO_BE_ANALYZED|1
) was also used to annotate the service, but this time is an internal code that will be used by the subscriber to recognize the entity that it has to analyze.
Now, the Publisher can initialize the encryption metadata of the entity, sharing it with the healthcare party at the same time, and create it.
For simplicity's sake, the Publisher is getting the id of the healthcare party to share the data with from the responsible
field of the patient. This is a field automatically filled at patient creating with the id of the healthcare party who
created it. For more information about sharing data, check our tutorial on data sharing.
- Kotlin
- Python
- Typescript
- Dart
val recipientHcp = patient.responsible ?: throw IllegalStateException("Patient has no responsible")
val contactWithEncryptionMetadata = patientSdk.contact.withEncryptionMetadata(
base = contact,
patient = patient,
delegates = mapOf(recipientHcp to AccessLevel.Write)
)
patientSdk.contact.createContact(contactWithEncryptionMetadata)
recipient_hcp = patient.responsible
contact_with_encryption_metadata = patient_sdk.contact.with_encryption_metadata_blocking(
base=contact,
patient=patient,
delegates={recipient_hcp: AccessLevel.Write}
)
patient_sdk.contact.create_contact_blocking(contact_with_encryption_metadata)
const recipientHcp = patient.responsible
const contactWithEncryptionMetadata = await patientSdk.contact.withEncryptionMetadata(
contact,
patient,
{ delegates: { [recipientHcp]: AccessLevel.Write}}
)
await patientSdk.contact.createContact(contactWithEncryptionMetadata)
final recipientHcp = patient.responsible;
if (recipientHcp == null) {
throw ArgumentError("Patient has no responsible");
}
final contactWithEncryptionMetadata = await patientSdk.contact.withEncryptionMetadata(
contact,
patient,
delegates: { recipientHcp: AccessLevel.write }
);
return patientSdk.contact.createContact(contactWithEncryptionMetadata);
At the same time, the Subscriber will immediately receive the created entity and will perform its analysis.
Review the Analysis Result​
In the full code example available on the repository, the user will be able to register multiple glycemia measurements. After that, they will be asked if they want to display the results of the analysis performed by the Subscriber, that will add a SNOMED-CT code for the diagnosis and an internal code to signal that a Service has been analyzed.
To retrieve the Service where the Subscriber completed the analysis, the Publisher creates a filter.
- Kotlin
- Python
- Typescript
- Dart
val filter = ServiceFilters.byTagAndValueDateForSelf(
tagType = "CARDINAL",
tagCode = "ANALYZED"
)
val serviceIterator = patientSdk.contact.filterServicesBy(filter)
service_filter = ServiceFilters.by_tag_and_value_date_for_self(
tag_type="CARDINAL",
tag_code="ANALYZED"
)
service_iterator = patient_sdk.contact.filter_services_by_blocking(service_filter)
const filter = ServiceFilters.byTagAndValueDateForSelf(
"CARDINAL",
{ tagCode: "ANALYZED" }
)
const serviceIterator = await patientSdk.contact.filterServicesBy(filter)
final filter = await ServiceFilters.byTagAndValueDateForSelf(
"CARDINAL",
tagCode: "ANALYZED"
);
final serviceIterator = await patientSdk.contact.filterServicesBy(filter);
This filter will retrieve all the services shared with the Patient that have in the tag field a CodeStub with type CARDINAL
and code ANALYZED
.
Finally, for each retrieved service, the first CodeStub with type SNOMED
is taken, the full Code is retrieved from the
cloud and the label is shown.
- Kotlin
- Python
- Typescript
- Dart
while(serviceIterator.hasNext()) {
val service = serviceIterator.next(1).first()
val diagnosisOrNull = service.tags.firstOrNull { it.type == "SNOMED" }
if (diagnosisOrNull != null) {
val code = patientSdk.code.getCode(diagnosisOrNull.id!!)
println("The diagnosis for sample ${service.id} is ${code.label?.getValue("en")}")
} else {
println("No diagnosis for this sample")
}
}
while service_iterator.has_next_blocking():
service = service_iterator.next_blocking(1)[0]
diagnosis_or_none = next((tag for tag in service.tags if tag.type == "SNOMED"), None)
if diagnosis_or_none is not None:
code = patient_sdk.code.get_code_blocking(diagnosis_or_none.id)
print(f"The diagnosis for sample {service.id} is {code.label['en']}")
else:
print("No diagnosis for this sample")
while(await serviceIterator.hasNext()) {
const service = (await serviceIterator.next(1))[0]
const diagnosisOrNull = service.tags.find( it => it.type == "SNOMED")
if (diagnosisOrNull !== undefined) {
const code = await patientSdk.code.getCode(diagnosisOrNull.id)
console.log(`The diagnosis for sample ${service.id} is ${code.label["en"]}`)
} else {
console.log("No diagnosis for this sample")
}
}
List<String> diagnoses = [];
while(await serviceIterator.hasNext()) {
final service = (await serviceIterator.next(1)).first;
final diagnosisOrNull = service.tags.firstWhereOrNull((tag) => tag.type == "SNOMED");
if (diagnosisOrNull != null) {
final code = await patientSdk.code.getCode(diagnosisOrNull.id!);
diagnoses.add("The diagnosis for sample ${service.id} is ${code.label?["en"]}");
}
}