The Cardinal data model
Overview​
The Cardinal Data Model is designed to support a variety of use cases within the digital health space. It is a fixed data model, meaning that users cannot define custom models. This decision enhances both the speed and robustness of interoperability and encryption.
In this section, we outline the most commonly used entities, their use cases, and properties. For a full list of entities, refer to the reference documentation (🚧).
Root-level Entities​
Root level entities are the one that can be manipulated using basic operations. Each root-level entity has its own section in the SDK, that exposes the operations that can be applied to the entity.
- Kotlin
- Python
- Typescript
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")
)
// Accessing the section for the HealthElement entity to retrieve a single HealthElement by id
val healthElement = sdk.healthElement.getHealthElement(healthElementId)
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")
)
# Accessing the section for the HealthElement entity to retrieve a single HealthElement by id
health_element = sdk.health_element.get_health_element_blocking(health_element_id)
const CARDINAL_URL = "https://api.icure.cloud"
const username = await readLn("Login: ")
const password = await readLn("Password: ")
const sdk = CardinalSdk.initialize(
undefined,
CARDINAL_URL,
new AuthenticationMethod.UsingCredentials.UsernamePassword(username, password),
StorageFacade.usingFileSystem("./scratch/storage")
)
// Accessing the section for the HealthElement entity to retrieve a single HealthElement by id
const healthElement = await sdk.healthElement.getHealthElement(healthElementId)
Base Entities​
Base entities do not contain sensitive data and are therefore unencrypted. Any user with appropriate permissions can create, search, and delete these entities. Since they are not encrypted, there are no encryption metadata fields to manage, and sharing the entity's ID is sufficient for access.
- Code: Represents a concept from a medical codification system (e.g., SNOMED-CT, LOINC), or an internal codification. Codes support versioning and multilingual content.
- Device: Represents a medical device (e.g., MRI machine, smartwatch, any connected device that monitors a patient). When associated with a user, a device can log in and create, search, and share data, similar to other data owners.
- HealthcareParty: Represents any actor involved in patient care, such as a doctor, nurse, receptionist, or medical institution. A HealthcareParty can be associated with a user to log in and manage data.
- User: An entity that represents an actor able to log in to Cardinal. It can be linked to a HealthcareParty, Patient, or Device entity.
A registered user can log in but will not be able to create, retrieve, search, or share encryptable entities unless they are linked to a HealthcareParty, Patient, or Device. A user linked with one of these entities is called a Data Owner.
Encrypted Entities​
Encryptable entities contain sensitive data and are encrypted on the client side before being stored in the cloud. Only Data Owners can create, share, retrieve, or delete these entities. Even if a non-Data Owner has permission to access an entity (e.g., an admin), they will only have access to the unencrypted portion of the data.
- Contact: a Contact is an encryptable entity that represents a situation that involves a patient where medical data is created. Usually it involves a doctor (healthcare party), like in the case of a medical examination.
- Document: a Document is an encryptable entity that is used to store a medical document in any format. It can contain textual document, images, and binary data, that are also encrypted before being stored on the cloud. Document entities can store large quantities of data while keeping efficiency in searching the data.
- HealthElement: Represents a medical condition, ranging from short-term conditions (e.g., fever) to chronic ones (e.g., allergies).
- Message: Used for encrypted message exchanges between users. It supports both text and file attachments, all of which are encrypted.
- Patient: Represents a patient, the subject of treatment or medical data collection. A Patient entity can be linked to a User to allow login.
- Topic: Used with the Message entity to support encrypted conversations between users.
Nested Entities​
Nested entities cannot exist independently and are part of another entity. If they are encryptable, a user who can decrypt the parent entity can also decrypt its nested entities.
- CodeStub: a CodeStub is a non-encryptable entity that represent a
reference to a Code. It can be nested in many different entities, most commonly
in the
tag
andcode
filter, and it is used to link a Code to it without having to include the whole Code. - Content: a Content is an encryptable entity that is nested into a Service and contains the value of the medical data. It supports scalar numeric values, multidimensional arrays, binary data and references to big files stored in Documents.
- Identifier: an Identifier is a non-encryptable entity that can be used to connect resources with external contents, as per the FHIR specification.
- Service: a Service is an encryptable entity nested inside a Contact and represent an instance of medical data collected in it. Multiple Services can be associated to the same contact if multiple measurements are taken in the same session.
- SubContact: a SubContact is an encryptable entity that is embedded into a Contact and can provide additional medical context to it and to the Service it contains.
Shared Fields​
The following fields are common across most entities.
Id​
A unique identifier for an entity. It is recommended to use a UUID v4
to avoid conflicts. The exception is the Code entity, where the ID format
is type|code|version
.
Rev​
An internal fields that represents the version of the entity, in the format <number>-<hash>
. This field is managed
internally by Cardinal and is not present on nested entities.
When you create an entity, its rev will start from 1, and it will increase with each operation that modifies or deletes it. To modify an entity, you will always have to start from the latest revision of the entity otherwise an exception will be thrown. All the methods that allow you to retrieve entities by id or using other search criteria will return the latest revision of each entity.
Tag and Code​
tag
and code
are collections of CodeStub (i.e. a reference to a Code)
that can be used to add to an entity some context that can be used after to query the data.
The difference between tag
and code
is purely conceptual: tag
is strictly for non-sensitive context while the
CodeStubs in code
may contain sensitive data. For example, a CodeStub for the region a patient lives in would go in
tag
, while a CodeStub on a Contact for the department of the hospital where an examination was performed would go in
code
.
By default, both tag
and code
are unencrypted to allow you to be used in searching. However, if you feel that the
CodeStub in code
may leak sensitive data, you should configure them to be encrypted in the encryption manifest.
Note that if you do so, the CodeStubs in codes will not available anymore for querying.
Internal Metadata Fields​
All the non-nested encryptable entities contain several field dedicated to handling encryption: (secretForeignKeys
,
cryptedForeignKeys
, delegations
, encryptionKeys
, encryptedSelf
, securityMetadata
). These fields are managed
internally and must not be modified manually. Any attempt of modifying those fields in an entity will be refused by the
backend.
Created​
Where present, the created
field contains the timestamp of creation of the entity. It is automatically filled by the
backend when the entity is created.
Modified​
The modified
field contains the timestamp of the last modification of the entity. Differently from the created
field,
it is not handled automatically by the backend.
Author​
Where present, the author
field contains the id of the User that created the entity. It is automatically filled by the
backend when the user is created.
Responsible​
Where present, the responsible
field contains the id of the Data Owner (HealthcareParty, Patient, or Device) that
created the entity. The backend will automatically fill this field when the entity is created. However, a Data Owner
can opt out when creating the sdk to avoid creating an
unencrypted connection with the Contact. This may be the case for Patient users or when HealthcareParty users are
defined as anonymous data owners.
Deletion Date​
Where present, the deletionDate
field contains the timestamp of soft-deletion of an entity. For more information
about deleting entities, check the related how to.
Identifiers​
A collection of Identifier that can be used to connect an entity with external resources as per the FHIR specification.
Internal Unstructured Data Types​
The information in some field are not represented through their own data type, but as primitive type with some rules
for formatting. An example of this is the dateOfBirth
field in the Patient entity: it is a date, precise to the day,
stored as a number in the YYYYMMDD
format.
Below, you will find a list of all such types.
FuzzyDate​
A date, precise to the day, stored as a number in the YYYYMMDD
format. If either the month or day information is
unknown or unavailable, 00
should be used instead.
This format will be replaced by a data type in the near future.
FuzzyDateTime​
A timestamp, precise to the second, stored as a number in the YYYYMMDDhhmmss
format. If any part of the date, except
for the year is unknown 00
can be used instead. Therefore:
20240101230000
encodes2024/01/01
at 23 hours, but minutes and seconds are unknown20240101235960
encodes2024/01/02
at00:00:00
This format will be replaced by a data type in the near future.