Firebase cloud hosting infrastructure for the ENGAGE-HF project.
The base functionality of the ENGAGE-HF Firebase Functions revolve around three parts:
- recommending medication changes to patients based on existing medication, vitals and symptom scores
- calculating symptom scores from questionnaire responses of patients
- generating Health Summary PDFs containing recommendations, vitals and symptom scores
To use Firebase functions for your own project or to emulate them for client applications, this section will help to give an overview of the different packages in use and how to install, build, test and launch them.
This repository contains two separate packages.
- The package located in functions/models contains model types including decoding/encoding functions and useful extensions that are shared between the Firebase functions and the web dashboard. This package is released via the npm registry and can be accessed as
@stanfordbdhg/engagehf-models
. - The package located in functions contains the Firebase functions and services that are called from these functions. This package has a local dependency on the package in functions/models. Therefore, the functions package does not work (e.g. for linting, building, etc) without building the models package first.
To make this structure simpler to use, we provide different scripts as part of the package.json file in the root directory of this repository. The file ensures execution order between the two packages. We only document the scripts located in this file, since they cover the most common use cases, feel free to have a look at the individual package.json files of the respective packages to get a deeper understanding and more package-focused operations.
Command | Purpose |
---|---|
npm run install | Installs dependencies (incl. dev dependencies) for both packages. |
npm run clean | Cleans existing build artifacts for both packages. |
npm run build | Builds both packages. If you have added or removed files in one of the packages, make sure to clean before using this command. |
npm run lint | Lints both packages. Make sure to build before using this command. You may want to append :fix to fix existing issues automatically or :strict to make sure the command does not succeed with existing warnings or errors. |
npm run prepare | Combines cleaning, installing and building both packages. |
npm run test:ci | Tests the Firebase functions with emulators running and with test coverage collection active. |
npm run serve:seeded | Starts up the relevant emulators for ENGAGE-HF and seeds them. Make sure to build the project first before executing this command. |
For using the emulators for client applications, it is probably easiest to call npm run prepare
whenever files could have changed (e.g. when changing branch or pulling new changes) and then calling npm run serve:seeded
to start up the emulators in a seeded state. Both of these commands are performed in the root directory of this repository.
Otherwise, you may want to use Docker to run the emulators. For this, you can use the following command:
docker-compose up
This can be especially useful if you're using an operating system like Windows, as scripts contain OS-specific commands that may not work the same way across different platforms.
The ENGAGE-HF web frontend uses single sign on (SSO) as a mechanism to allow clinicians and admins to log into the web page.
For a Stanford deployment, the Stanford SAML and OIDC Configuration Manager needs to be configured using an OIDC configuration. Other sites can obtain the OpenID Connect (OIDC) from other sites following a similiar setup. You will use the Client ID and Client secret from the configuration to set up the OIDC authentication in Firebase Authentication.
- Subject Type:
public
- Token Endpoint Auth:
client_secret_basic
- Grant Type:
refresh_token (authorization_code always enabled)
- Scopes:
profile
,email
. - Redirect URI(s):
https://FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
, replacingFIREBASE_PROJECT_ID
with your Firebase project identifier. - Disable PKCE:
true
. This needs to be set as Firebase doesn't support Proof Key for Code Exchange (PKCE) at this moment.
You will need to configure Firebase Authentication with Identity Platform to use OpenID connect in web apps. You need to configure the OpenID Connect Sign-in provider as follows:
- Grant Type:
Code flow
- Name: e.g.,
Stanford
(or the name of the other institutions) - Client ID: Client ID obtained from your OIDC configuration from the Stanford SAML and OIDC Configuration Manager.
- Issuer (URL): e.g.,
https://login.stanford.edu
(or the issues URL from the other institution) - Client secret: Client ID obtained from your OIDC configuration from the Stanford SAML and OIDC Configuration Manager.
Health Summary PDFs contain four sections:
- Medications & Recommendations
- Vital Summary containing only the most important vitals
- Symptom Score Report including a speedometer displaying the current score in relation to the previous one and a baseline, a table with the most recent symptom score results and a small personal summary
- Detailed Vitals Page with graphs displaying body weight, heart rate and blood pressure measurements and important key figures
ENGAGE-HF uses four different algorithms to recommend medication changes based on existing medication, vitals and symptom scores. Each recommendation algorithm corresponds to a medication class (Beta blockers, MRA, SGLT2i) or a group of medication classes (RASI including ACEI, ARB and ARNI).
- Note 1: This codebase contains five different recommender types, including one for diuretics. The diurectics recommender simply recommends staying on the existing medication and assumes that medication to already be at the personal target dose.
- Note 2: The algorithms are only expected to be run with one medication per class, not multiple different medications in one class.
ENGAGE-HF utilizes four algorithms to recommend medication adjustments based on a patient's current medications, vital signs, and symptom scores. Each algorithm is tailored to a specific medication class — Beta-blockers, MRAs, and SGLT2 inhibitors — or a combination of classes, such as RASI (including ACEIs, ARBs, and ARNIs).
Notes:
- The codebase includes an additional recommender for diuretics, making a total of five recommenders. The diuretics recommender simply advises maintaining the current medication, assuming it is already at the patient’s personal target dose.
- The algorithms are designed to handle one medication per class, not multiple medications within the same class.
Depending on contraindications entered on the Web Dashboard, a different medication is listed in the following order:
- Carvedilol
- Metoprolol Succinate
- Bisoprolol
Depending on contraindications entered on the Web Dashboard, a different medication is listed in the following order:
- Spironolactone
- Eplerenone
Depending on contraindications entered on the Web Dashboard, a different medication is listed in the following order:
- Empagliflozin
- Dapagliflozin
- Sotagliflozin
Contraindications are handled using FHIR AllergyIntolerance resources found in the users/$userId$/allergyIntolerances collections. In the code
property, we use RxNorm codes as described in functions/src/tests/resources/contraindications.csv for the different medications. Depending on the type
and criticality
properties, we identify each allergy as one of the following:
- Severe Allergy (for type
allergy
and criticalityhigh
) - Allergy (for type
allergy
and criticality nothigh
) - Intolerance (for type
intolerance
) - Financial (for non-standard type
financial
)
In the file, we also describe how each of these cases relate to which medications are taken out of the recommendable options.
The Kansas City Cardiomyopathy Questionnaire-12 (KCCQ-12) score assesses a patient’s physical limitations, symptom frequency, quality of life, and social limitations. Scores are normalized to a 0-100 scale, where higher values indicate better health status. ENGAGE-HF further adds one additional question about dizziness to the questionnaire used in the application.
Questions 1a, 1b, and 1c assess physical limitations. Responses are scored from 1 to 6, where:
- 1 indicates severe limitations.
- 5 indicates minimal limitations.
- 6 indicates "Does not apply" (excluded from scoring).
The Physical Limitations score is the average of the applicable responses, normalized to a 0-100 scale. If fewer than two responses are valid, the score cannot be calculated.
Questions 2, 3, 4, and 5 evaluate symptom frequency, with responses scored from:
- 1 to 4 for Questions 2 and 5.
- 1 to 7 for Questions 3 and 4.
Each response is normalized to a 0-100 scale. The Symptom Frequency score is the average of these normalized values.
Questions 6 and 7 measure quality of life, with responses scored from 1 to 4, where lower scores indicate worse quality of life. These scores are normalized to a 0-100 scale, and their average forms the Quality of Life score.
Questions 8a, 8b, and 8c address social limitations, scored similarly to the Physical Limitations questions (1 to 6 scale, excluding "Does not apply"). The Social Limitations score is the average of the applicable responses, normalized to 0-100, provided at least two valid responses are available.
The Clinical Summary Score is the average of the Physical Limitations and Symptom Frequency scores, whereas the Overall Total Score is calculated as the average of all four above-mentioned domain scores.
In addition to the KCCQ-12 questions, ENGAGE-HF uses question 9 to ask the patient about dizziness. Its responses are encoded as integers between 1 and 5.
This document describes how data is stored in Firestore for the Engage-HF app.
The following section describes custom types defined for this system to be later used
A LocalizedText object shall be used whenever text requires localization or may require in the future. The object may either simply be a single string (in the case of localizations not being available yet) or a dictionary with string keys and string values. If LocalizedText is represented by a string-string dictionary, the keys represent language-codes following the ISO 639-1 (or if necessary ISO 639-2) standard and the respective text in the given language as value.
Some localizations may require to include regions as well (e.g. Australian/British/American English), so the clients should be aware of this and prioritize the language code with the correct region (en-us
) over the general code (en
), if it is present.
A LocalizedText object cannot be used in FHIR-conforming types due to its incompatibility with the standard. FHIR types commonly contain text in one language only to be specified using the language
property.
{
"en":"Welcome to the Engage-HF app!",
"de":"Willkommen in der Engage-HF App!",
"es":"¡Bienvenido a la aplicación Engage-HF!"
}
When a user joins Engage-HF, we first create an invitation code on demand of an operator of the web dashboard.
For patients, upon entering the invitation code using an anonymous login, the userId is set. After successful registration, the user object will be fully created and the invitation is removed from this collection.
For SSO users (e.g. clinicians,owners), upon the first login using SSO, the related invitation is fetched, the user object will be fully created and the invitation is removed from this collection.
Property | Type | Values | Comments |
---|---|---|---|
code | string | e.g. 'PAULPAUL' | An 8-digit invitation code or SSO email address. |
userId | optional string | - | The userId associated with the invitation. This is set when an anonymous user has entered an invitation code, but has not used a proper account to log in yet. |
auth | optional Auth | Authentication information to be set when redeeming invitation. Will no longer be set once invitation has been redeemed. | |
auth>displayName | optional string | Display name for the user. | |
auth>email | optional string | E-Mail address of the user. | |
auth>phoneNumber | optional string | Phone number of the user. | |
auth>photoURL | optional string | URL for a photo of the user. | |
user | optional User | See users/$userId$ for full specification. Will no longer be set once invitation has been redeemed. |
In this section, we describe information regarding all the medications to be specified in the Engage-HF context. These medications may be used by a clinician for medication requests to a patient (users/$userId$/medicationRequests) or contra-indications (users/$userId$/allergyIntolerances). The medications are generated from functions/data/medicationCodes.json file containing medications (incl. respective RxNorm SCD type codes) grouped by medication classes.
Based on FHIR Medication, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
code | FHIRCodeableConcept | - | FHIRCodeableConcept containing an RxNorm code of the IN/MIN type, a display name and the RxNorm system url. |
extension | list of Extension | - | See /medications/$medicationId$/extension for possible values |
Based on the Extension format specified in FHIR, a medication may contain a list of these following extension properties. Each property will need to get a url assigned to fit the FHIR data format.
Property | Type | Values | Comments |
---|---|---|---|
medicationClass | string | - | A medicationClassId referring to a medicationClass specified in /medicationClasses/$medicationClassId$. One medication object may contain multiple medicationClass extension properties. |
minimumDailyDose | SimpleQuantity | - | Unit: mg/day. Occurs exactly once. Multi-ingredient tablets contain an array of double rather than a double. |
targetDailyDose | SimpleQuantity | - | Unit: mg/day. Occurs exactly once. Multi-ingredient tablets contain an array of double rather than a double. |
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
code | FHIRCodeableConcept | - | FHIRCodeableConcept containing an RxNorm code of the SCD type, a display name and the RxNorm system url. |
ingredient | list of Ingredient | - | Use references to medications and strength for quantity information. |
ingredient[x]>itemCodeableConcept | FHIRCodeableConcept | - | FHIRCodeableConcept containing an RxNorm code of the IN type, a display name and the RxNorm system url. |
ingredient[x]>strength | Ratio | - | Uses "mg" as numerator unit, no denominator unit and denominator value is always 1. |
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | - |
name | LocalizedText | - | A name for a given medicationClass to be displayed to a user. |
videoPath | string | e.g. "/videoSectionId/1/videos/2" | The path to retrieve the respective video from Firestore. |
Property | Type | Values | Comments |
---|---|---|---|
name | string | e.g. "Stanford University" | - |
contactName | string | e.g. "Alex Sandhu, MD" | - |
phoneNumber | string | e.g. "(650) 493-5000" | - |
emailAddress | string | e.g. "dothfteam@stanford.edu" | - |
ssoProviderId | string | - | The providerId as used for single sign-on. |
In this section, we describe all the information stored for questionnaires.
Based on FHIR Questionnaire, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
meta | Meta | - | Resource: Metadata about the resource |
identifier | list of Identifier | - | Business identifier for this questionnaire. There may be multiple questionnaire objects in this list, with differing id and language properties, but a common business identifier. |
title | string | - | Human-friendly title of the questionnaire. |
date | Date | - | last modified date |
version | string | - | Business version of the questionnaire. |
language | optional string | e.g. "en" | Following IETF BCP-47 / FHIR ValueSet languages |
item | list of BackboneElement | - | items as defined in FHIR Questionnaire |
item[x]>linkId | string | - | Unique id for item in questionnaire |
item[x]>definition | optional uri | - | Details for the item |
item[x]>prefix | optional string | e.g. "1(a)", "2.5.3" | Prefix of the item that isn't actually part of the name but may still be displayed. |
item[x]>text | string | - | Primary text for the item |
item[x]>type | code | e.g. "group", "display", "boolean", "decimal", "integer", "date", etc | See QuestionnaireItemType for available values. |
You can find an example KCCQ-12 questionnaire in functions/data/questionnaires.json.
In this section, we describe all user-related data to be stored. The security rules shall be set up in a way to only allow for a patient to access its own information and a clinician to access all patients' information (or even more restrictive, if needed).
Property | Type | Values | Comments |
---|---|---|---|
type | string | e.g. "admin", "owners", "clinician", "patient" | The type of the user. |
dateOfEnrollment | Date | - | The date when the invitation code was used to create this user. |
dateOfBirth | optional Date | - | The date when the user was born. |
genderIdentity | optional string | "female","male","transgender","nonBinary","preferNotToState" | The gender identity chosen when a patient redeemed the invitation. |
organization | optional string | - | The id of the organization a clinician, patient or owner is associated with. |
invitationCode | string | - | The invitationCode to be used when logging in to the app for the first time. |
language | optional string | e.g. "en" | Following IETF BCP-47 / FHIR ValueSet languages. |
receivesAppointmentReminders | optional boolean | true, false | Decides whether to send out appointment reminders one day before each appointment. |
receivesInactivityReminders | optional boolean | true, false | Decides whether to send updates about inactivity. |
receivesMedicationUpdates | optional boolean | true, false | Decides whether to send updates about current medication changes. |
receivesQuestionnaireReminders | optional boolean | true, false | Decides whether to send reminders about filling out their questionnaire every 14 days. |
receivesRecommendationUpdates | optional boolean | true, false | Decides whether to send updates about recommended medication changes. |
receivesVitalsReminders | optional boolean | true, false | Decides whether to send reminders about measuring vitals. |
receivesWeightAlerts | optional boolean | true, false | Decides whether to send out alerts when drastic weight changes are observed. |
timeZone | string | e.g. "America/Los_Angeles" | The value needs to correspond to an identifier from TZDB. It must not be an offset to UTC/GMT, since that wouldn't work well with daylight-savings (even if there is no daylight-savings time at that location). Also, don't use common abbreviations like PST, PDT, CEST, etc (they may be ambiguous, e.g. CST). If the timeZone is unknown, then "America/Los_Angeles" should be used. |
This data is required to send push notifications over the Firebase Cloud Messaging System.
Property | Type | Values | Comments |
---|---|---|---|
modifiedDate | Date | - | This date is updated whenever the token is sent to the server, even if it is not replaced by a different token. It simply reflects the last date we can definitely confirm the token was active. Idea: We may ignore some devices, if the token has not been updated for a long time, since the app has not been opened for a long time. |
notificationToken | string | - | The FCM token as received from Firebase in the app. |
platform | optional string | e.g. "iOS", "Android" | This information is important as context for the osVersion , appVersion and appBuild properties. |
osVersion | optional string | e.g. "17.5.1" | The version of the OS. Depending on the OS, they may have different formats to be specified separately. |
appVersion | optional string | e.g. "1.0.1" | The version of the app as it is specified on the App/Play Store. |
appBuild | optional string | e.g. "56" | The build version of the app as defined by the CI pipeline releasing the app. |
language | optional string | e.g. "en" | Following IETF BCP-47 / FHIR ValueSet languages |
Push notifications over APNS / FCM only contain text in a single language. For this, the device's language
property shall be prioritized, falling back on the user's language
property and using US-English if both are not present.
A device can only be identified by its notification token. When updating a device's information, we therefore check if the notification token already exists in Firestore. If it already exists, we update all other existing fields' values - otherwise, we create a new device.
A device may receive a different notification token at any time though. Therefore, we might create a new device, even though that device already exists in the table with a different (now inactive) notification token. Therefore, every time we send out a new notification and receive the information that a token is no longer active, we need to remove the device from this table.
Only users with a patient
role assigned have values in this collection.
Based on FHIR AllergyIntolerance, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
type | optional allergyIntoleranceType | e.g. "allergy", "intolerance" | In addition to the FHIR defined value set, we also use "financial" - these values shall not be exposed to EHR systems. |
code | CodableContent | e.g. {"coding":[{"system":"https://hl7.org/fhir/R4B/valueset-allergyintolerance-code.html","code":"293963004","display":"Cardioselective beta-blocker allergy"}],"text":"Cardioselective beta-blocker allergy"} |
Uses either AllergyIntoleranceCode, medicationId as used in /medications/$medicationId$ and/or medicationId as used in /medicationClasses/$medicationClassId$. |
patient | string | - | userId as used in /users/$userId$ and related collections. |
We use RxNorm codes to identify contraindications using the following rules:
Class | Medication | If allergy, not eligible for | If allergy with reaction type angioedema not eligible for | If intolerance not eligible for | If intolerance default to | If financial, not eligible for |
---|---|---|---|---|---|---|
BB | Metoprolol succinate ER | BB | BB | Metoprolol | Carvedilol | BB |
BB | Carvedilol | BB | BB | Carvedilol | Metoprolol succinate | BB |
BB | Carvedilol phosphate ER | BB | BB | Carvedilol | Metoprolol succinate | BB |
BB | Bisoprolol | BB | BB | Bisoprolol | Carvedilol | BB |
SGLT | Dapagliflozin | SGLT | SGLT | Dapagliflozin | Empagliflozin | SGLT |
SGLT | Empagliflozin | SGLT | SGLT | Empagliflozin | Dapagliflozin | SGLT |
SGLT | Sotagliflozin | SGLT | SGLT | Sotagliflozin | Empagliflozin | SGLT |
SGLT | Bexagliflozin | SGLT | SGLT | Bexagliflozin | Empagliflozin | SGLT |
SGLT | Canagliflozin | SGLT | SGLT | Canagliflozin | Empagliflozin | SGLT |
SGLT | Ertugliflozin | SGLT | SGLT | Ertugliflozin | Empagliflozin | SGLT |
MRA | Spironolactone | MRA | MRA | Spironolactone | Eplerenone | MRA |
MRA | Eplerenone | MRA | MRA | Eplerenone | Spironolactone | MRA |
ACE | Quinapril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Perindopril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Ramipril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Benazepril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Captopril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Enalapril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Lisinopril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Fosinopril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Trandolapril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ACE | Moexepril | ACEI | ACEI/ARB/ARNI | ACEI | Valsartan/ARNI | ACEI/ARB/ARNI |
ARB | Losartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Valsartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Candesartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Irbesartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Telmisartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Olmesartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Azilsartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARB | Eprosartan | ARB/ARNI | ACEI/ARB/ARNI | ARB/ARNI | Lisinopril | ACEI/ARB/ARNI |
ARNI | Sacubitril-Valsartan | ARB/ARNI | ACEI/ARB/ARNI | ARNI | Valsartan | ARNI |
Diuretic | Furosemide | - | - | - | - | - |
Diuretic | Bumetanide | - | - | - | - | - |
Diuretic | Torsemide | - | - | - | - | - |
Diuretic | Ethacrynic Acid | - | - | - | - | - |
Only users with a patient
role assigned have values in this collection. There is currently no easy way to check for appointments relevant to one clinician - if it happens to be a requirement in the future, we may need to restructure this.
Based on FHIR Appointment, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
status | AppointmentStatus | e.g. "booked" | - |
created | Date | ||
start | Instant | - | - |
end | Instant | - | - |
comment | optional string | - | May not be shown to the patient. |
patientInstruction | optional string | - | May be shown to the patient. |
participant | list of CodeableConcept | - | Must contain at least one element. |
participant[x]>actor | Reference(User) | e.g. users/123 |
The Firestore path to the patient. |
participant[x]>status | ParticipationStatus | e.g. accepted, declined, tentative, needs-action | - |
Only users with a patient
role assigned have values in this collection.
Based on FHIR MedicationRequest, the following properties may be used, while additional properties are ignored by the Engage-HF system. Clients may ignore this list and simply query for users/$userId$/medicationRecommendations and retrieve the relevant requests from the recommendations.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
medication | Reference(Medication) or CodeableConcept | - | CodeableConcept containing one of the codes from /medications/$medicationId$ |
dosageInstruction | Dosage | - | - |
The dosageInstruction
property may contain values with the following properties:
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
text | optional string | - | Free text dosage instructions |
additionalInstruction | list of CodableConcept | information like "with meals", "may cause drowsiness", etc | Supplemental instruction or warnings to the patient |
patientInstruction | optional string | - | Patient or consumer oriented instructions |
timing | optional Timing | - | When medication should be administered |
doseAndRate | list of Element | - | Amount of medication administered |
doseAndRate>type | optional DoseRateType | e.g. "calculated", "ordered" | |
doseAndRate>dose | optional SimpleQuantity | - | Unit is always "tablet". Value is usually 0.5, 1 or 2. |
maxDosePerPeriod | optional Ratio | - | Upper limit on medication per unit of time |
maxDosePerAdministration | optional SimpleQuantity | - | Upper limit on medication per administration |
maxDosePerLifetime | optional SimpleQuantity | - | Upper limit on medication per unit of time |
There may be up to three intakes per day (morning, mid-day and evening) with either 0.5, 1 or 2 tablets. The dosages should always be grouped by medication, i.e. there should not be multiple medication elements concerning the same medication but different dosage instructions. Instead, one medication element shall be used for that medication with multiple dosage instructions.
Only users with a patient
role assigned have values in this collection.
These are the output values of the recommendation algorithms. Depending on the type, the recommendations should be displayed with different icons and texts.
Property | Type | Values | Comments |
---|---|---|---|
currentMedication | optional Reference(FHIRMedicationRequest) | e.g. {"reference":"users/123/medicationRequest/2"} |
Reference to the existing medication request, if applicable. |
recommendedMedication | optional Reference(FHIRMedication) | e.g. {"reference":"medications/2"} |
Reference to the recommended medication, if applicable. This should always direct to a medication, not a drug. |
displayInformation | DisplayInformation | - | The information necessary for the client to display the medication recommendation. |
Type | Icon | Current Medication | Recommended Medication | Comments |
---|---|---|---|---|
targetDoseReached | Green Checkmark | exists | undefined | The target dose has been reached. |
personalTargetDoseReached | Green Checkmark | exists | undefined | The personal target dose has been reached, meaning that vitals signal that we should not increase the dose (yet). |
improvementAvailable | Yellow Up Arrow | exists | undefined | The patient should uptitrate. |
morePatientObservationsRequired | Yellow | maybe | maybe (mutually exclusive to current medication) | There are not enough patient observations to recommend anything. They should probably do some blood pressure measuring. |
moreLabObservationsRequired | Yellow | maybe | maybe (mutually exclusive to current medication) | More lab observations are required to recommend anything. The patient should probably schedule an appointment with a clinician. |
notStarted | Gray Up Arrow | undefined | exists | Medication has not been started, but is eligible for initiation. |
noActionRequired | Gray | undefined | exists | The recommended medication is not eligible, but should still be shown to the user as an option without recommending it to them. |
Diuretics, if currently present as medication request, will be shown as a recommendation with personalTargetDoseReached
, so that the logic on the client becomes easier.
Property | Type | Values | Comments |
---|---|---|---|
title | LocalizedText | e.g. "Carvedilol" | The name of the medication (not the tablet), e.g. coming from medications/1998 . |
subtitle | LocalizedText | e.g. "Beta Blockers" | The medication class name, e.g. coming from medicationClasses/0 . |
description | LocalizedText | e.g. "Personal target dose reached. No action required." | The explanation of the recommendation, displayed along with a summary of the medication. |
type | Medication Recommendation Type | e.g. "personalTargetDoseReached" | See Medication Recommendation Type for more information. |
videoPath | optional string | e.g. videoSections/1/videos/3 |
This is the video to show when the recommendation is tapped. The clients may want to hide the icon to get to the video when this value is not present. |
dosageInformation | DosageInformation | See Dosage Information | A description of the current, minimum, and target doses for a given medication. |
When the patient is not yet taking the medication, its currentSchedule will be an empty list. |
The DosageInformation
property contains the following information:
Property | Type | Values | Comments |
---|---|---|---|
currentSchedule | list of DoseSchedule | e.g. [25mg twice daily, 10mg daily] | A list tracking how many times per day the patient currently takes each tablet. |
minimumSchedule | list of DoseSchedule | e.g. [6.25mg daily] | A list tracking how many times per day the patient would take each tablet on a minimal dose. |
targetSchedule | list of DoseSchedule | e.g. [6.25mg daily] | A list tracking how many times per day the patient would take each tablet on a maximal dose. |
unit | string | e.g. "mg" | The unit by which the ingredients in the medications are measured, as found in medications/$medicationId$/drugs/$drugId$ . |
The DoseSchedule
object describes the number of tablets taken per day for a single medication as described in medications/$medicationId$/drugs/$drugId$
. For example, taking two 5mg tablets in the morning and one 15mg tablet in the afternoon would result in two schedules: 5mg twice daily, and 15mg daily. Compound (multi-ingredient) tablets have multiple quantities per tablet, and will be displayed as e.g. 24/26mg twice daily. DoseSchedule
has the following properties:
Property | Type | Values | Comments |
---|---|---|---|
frequency | number | e.g. 1, 2, or 0.5 | The number of tablets per day (as described by medicationRequests/$medicationRequestId$/dosageInstruction ). |
quantity | list of number | e.g. [24, 26] or [6.25] | The amount of each active ingredient per tablet. |
Only users with a patient
role assigned have values in these collections.
We are storing different observations grouped by their type, e.g. we use /users/$userId$/bodyWeightObservations/$observationId$ for body weight observations and /users/$userId$/bloodPressureObservations/$observationId$ for blood pressure observations. The different collections are all listed below and they follow the specification of a FHIR observation as defined here with more strict specification following in the respective subsections.
Based on FHIR Observation, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
status | ObservationStatus | e.g. "final" | This value is most likely "final" in every case. |
code | Code | e.g. {"system":"http://loinc.org","code":"55423-8","display":"Number of steps"} |
Use one of the codes mentioned below. |
value | optional Quantity | e.g. {"code":"mm[Hg]","system":"http://unitsofmeasure.org","unit":"mmHg","value":120} |
Use one of the units listed below. |
component | list of components | - | Instead of containing a single value property, some observations are composed of multiple components (e.g. a blood pressure observation contains a diastolic and systolic component). |
component[x]>code | Code | e.g. {"system":"http://loinc.org","code":"55423-8","display":"Number of steps"} |
Use one of the codes mentioned below. |
component[x]>value | optional Quantity | e.g. {"code":"mm[Hg]","system":"http://unitsofmeasure.org","unit":"mmHg","value":120} |
Use one of the units listed below. |
effective | Period with Date objects or Date | - | Use Date for instant observation and periods for longer observations (e.g. more than 1 minute). |
Blood pressure observations contain the following code and no value.
code>system | code>code | code>display |
---|---|---|
"https://loinc.org" | "85354-9" | "Blood pressure panel with all children optional" |
Further, blood pressure observations have two components.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "8462-4" | "Diastolic blood pressure" | "http://unitsofmeasure.org" | double | "mm[Hg]" | "mmHg" |
"http://loinc.org" | "8480-6" | "Systolic blood pressure" | "http://unitsofmeasure.org" | double | "mm[Hg]" | "mmHg" |
Body weight observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "29463-7" | "Body weight" | double | "http://unitsofmeasure.org" | "kg" or "[lb_av]" | "kg" or "lbs" |
Creatinine observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "2160-0" | "Creatinine [Mass/volume] in Serum or Plasma" | double | "http://unitsofmeasure.org" | "mg/dL" | "mg/dL" |
Dry body weight observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "29463-7" | "Body weight" | double | "http://unitsofmeasure.org" | "kg" | "kg" |
Dry body weight observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "98979-8" | "Glomerular filtration rate/1.73 sq M.predicted [Volume Rate/Area] in Serum, Plasma or Blood by Creatinine-based formula (CKD-EPI 2021)" | double | "http://unitsofmeasure.org" | "mL/min/{1.73_m2}" | "mL/min/1.73_m2" |
Heart rate observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "8867-4" | "Heart rate" | double | "http://unitsofmeasure.org" | "/min" | "beats/minute" |
Potassium observations contain the following code and value.
code>system | code>code | code>display | value>system | value>value | value>code | value>unit |
---|---|---|---|---|---|---|
"http://loinc.org" | "6298-4" | "Potassium [Moles/volume] in Blood" | double | "http://unitsofmeasure.org" | "meq/L" | "mEq/L" |
All users are technically allowed to have values in this collection, although the requirements only contain patient-directed messages for now.
This data is used to display messages to a user (patient or clinician). For patients messages are describing recent changes in their respective data from clinicians (e.g. updated medication requests) or calls-to-action to the patient. Currently, there are no messages planned for the clinician, but they may be added in the future.
Property | Type | Values | Comments |
---|---|---|---|
dueDate | optional Date | - | The due date of the message to be shown in-app. |
completionDate | optional Date | - | Specifies when a message has been completed. TBD: Messages containing a completionDate may either be hidden on user's devices or be shown crossed out. |
type | optional string | e.g. "questionnaireReminder" | Some messages are sent out on a regular basis, where only the most recent message is really relevant for the patient (e.g. a reminder for a questionnaire). With this property, we can easily find existing messages of the same type and replace them with a new one, if necessary. |
title | LocalizedText | e.g. "Watch Welcome Video in Education Page." | May be localized. |
description | optional LocalizedText | e.g. "The video shows how you will be able to use this app." | May be localized. |
action | optional string | e.g. "videoSections/1/videos/0" | See "Message types". |
isDismissible | boolean | true,false | Whether or not the message is dismissible by the user or is solely controlled by the server. |
reference | optional string | - | Do not use this property as it is solely used to group messages in functions. |
The following list describes all different types a message could have. Expiration of messages should only be handled by the server, except for triggering the dismissMessage
Firebase function call that adds a completion date when the message is dismissible and has been dismissed by the user. A client doesn't need to know about the type
property, since we would otherwise need to check whether a new message type is supported by a client. It may also sort out message types unknown for the client's version.
Type | Trigger | Expiration | Action |
---|---|---|---|
MedicationChange | Server: /users/$userId$/medicationRequests changed for a given user. Maximum 1 per day. | Tap | videoSections/$videoSectionId$/videos/$videoId$ |
WeightGain | Server: New body weight observation received with 3 lbs increase over prior week's median. Do not trigger again for 7 days. | Tap | patients: medications, clinicians: users/$userId$/medications |
MedicationUptitration | Server: /users/$userId$/medicationRecommendations changed. | Tap | patients: medications, clinicians: users/$userId$/medications |
Welcome | Server: When creating new user. | Tap | videoSections/$videoSectionId$/videos/$videoId$ |
Vitals | Server: Daily at certain time (respect timezone!) | When receiving blood pressure and weight observations on the server from current day. | observations |
SymptomQuestionnaire | Server: Every 14 days. | After questionnaire responses received on server. | questionnaires/$questionnaireId$ |
PreAppointment | Server: Day (24h) before appointment. | After appointment time or when it is cancelled. | patients: healthSummary, clinicians: users/$userId$/appointments |
Inactivity | Server: Daily when lastActiveDate of a user is older than 7 days. | When a user observation is modified. | patients: null, clinician: users/$userId$ |
Only users with a patient
role assigned have values in this collection.
Based on FHIR QuestionnaireResponse, the following properties may be used, while additional properties are ignored by the Engage-HF system.
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | Resource: Logical id of this artifact |
questionnaire | string | - | canonical representation of the questionnaire, i.e. t |
author | string | - | The patient's userId |
authored | Date | - | The date the answers were gathered. |
status | QuestionnaireResponseStatus | - | Will most likely always be completed . |
item | list of Item | - | - |
item[x]>linkId | string | - | Pointer to specific item from questionnaire |
item[x]>definition | optional uri | - | details for item |
item[x]>text | optional string | - | Name for group or question text |
item[x]>answer | list of Answer | - | response(s) to the question |
item[x]>answer[y]>value | any | - | Value depending on the type of question in the survey, e.g. boolean, integer, date, string, etc |
Only users with a patient
role assigned have values in this collection.
Whenever a new questionnaire response is uploaded to Firestore, we calculate the score and keep the results here for easy retrieval later on. We also do not compute the score anywhere but simply refer to the scores listed in this collection.
Property | Type | Values | Comments |
---|---|---|---|
questionnaireResponseId | optional string | - | questionnaireResponseId as used in users/$userId$/questionnaireResponses/$questionnaireResponseId$ to be able to verify score calculations afterwards. It should be ignored by all clients. |
date | Date | - | must be equivalent to the date specified in the linked questionnaire response. |
overallScore | number | must be between 0 and 100 | - |
physicalLimitsScore | number | must be between 0 and 100 | - |
specificSymptomsScore | number | must be between 0 and 100 | - |
socialLimitsScore | number | must be between 0 and 100 | - |
qualityOfLifeScore | number | must be between 0 and 100 | - |
dizzinessScore | number | must be between 0 and 100 | - |
In this section, we describe all data related to educational videos to be shown in the Engage-HF mobile apps. The videos are grouped into different categories to be displayed as sections in the mobile apps.
Property | Type | Values | Comments |
---|---|---|---|
title | LocalizedText | e.g. "ENGAGE-HF Application" | May be localized. |
description | LocalizedText | e.g. "Helpful videos on the ENGAGE-HF mobile application." | May be localized. Are there different videos / videoSections for each platform? |
orderIndex | integer | e.g. 1 | Since Firestore collections aren't necessarily ordered, we have this property to order the elements by on the clients. The list is supposed to be ordered ascending by orderIndex . |
Property | Type | Values | Comments |
---|---|---|---|
title | LocalizedText | e.g. "Beta Blockers for Heart Failure" | May be localized. |
youtubeId | LocalizedText | e.g. "XfgcXkq61k0" | Contains the video id from YouTube. |
orderIndex | integer | e.g. 1 | Since Firestore collections aren't necessarily ordered, we have this property to order the elements by on the clients. The list is supposed to be ordered ascending by orderIndex . |
description | LocalizedText | - | Describes the content of the video; to be shown underneath the video. |
Embed links for YouTube: https://youtube.com/embed/${youtubeId}
.
Short links for YouTube: https://youtu.be/${youtubeId}
.
Watch links for YouTube: https://youtube.com/watch?v=${youtubeId}
.
Use createInvitation
to create invitations to be sent out to new patients, clinicians or owners.
An admin may create invitations for any user, an owner or clinician may only create invitations within their own organization and below or equal their own rank (i.e. clinicians cannot create owners, owners cannot create admins). Patients may not call this function.
Property | Type | Values | Comments |
---|---|---|---|
auth | object | - | Authentication information about the user to be invited. |
auth>displayName | optional string | - | A display name to use for the user. |
auth>email | optional string | - | An email address to use for the user. This is non-optional for invitations for owners and clinicians, since this email address is used as the invitation code for SSO. |
auth>phoneNumber | optional string | - | A phone number to use for the user. |
auth>photoURL | optional string | - | A photo URL to use for the user. |
user | object | - | A prepared user object to use for the enrollment of the user when using the invitation. It may contain the same properties as in the users collection, except for dateOfEnrollment and invitationCode . |
Property | Type | Values | Comments |
---|---|---|---|
id | string | - | The Firestore document id of the created invitation. |
Use customSeed
to seed Firestore with your own custom data with little effort. It may also be used to create users, which is not possible with the client SDK or to circumvent security rules temporarily before running tests, etc.
This function may only be called by admins. On emulators, this function can easily be triggered without authentication using an HTTP POST request.
Property | Type | Values | Comments |
---|---|---|---|
firestore | optional object | - | Custom data to seed firestore with. It needs to be nested at least two levels deep with the first level defining the collection (e.g. videoSections/0/videos ) and the second defining the document id (e.g. 1 ). |
users | optional object | - | Custom user creation using authentication information, user data (dateOfBirth, etc) and user collections (e.g. appointments, bodyWeightObservations, etc). |
users[x]>auth | object | - | User authentication information for login after the user has been created. |
users[x]>auth>uid | optional string | - | The user id to use when creating the user. When omitted, an id will be autogenerated. |
users[x]>auth>email | string | - | The email address to log into the user after it has been created. |
users[x]>auth>password | string | - | The password to log into the user after it has been created. |
users[x]>user | optional object | - | The user object that will be placed at users/$userId$ . This may be useful, when the userId is not specified, since the seeding function will make use of the auto-generated id. |
users[x]>collections | optional object | - | Similar to the firestore property, this content will be placed at the given path prefixed by users/$userId$/ . |
None.
Use defaultSeed
to seed Firestore with some default data. Depending on input, it may create users, invitations, static data (including videoSections, medications, etc) and/or fill existing user's collections.
This function may only be called by admins. On emulators, this function can easily be triggered without authentication using an HTTP POST request.
Property | Type | Values | Comments |
---|---|---|---|
date | string | - | An ISO 8601 formatted string of the date to seed data relative to. May be omitted, which will generate data relative to now. |
only | list of string | e.g. ["users","invitations"] | Allows to limit seeding data to the provided collections. |
onlyUserCollections | list of string | e.g. ["appointments","bodyWeightObservations"] | Allows to limit the creation of user collections for the created users to the provided collections. |
staticData | object | - | Allows to seed static data without calling updateStaticData separately. If not provided, static data will not be updated. |
userData | list of object | - | Allows to seed user collections to existing users. |
userData[x]>userId | string | - | The userId for the user to create seeding data for. |
userData[x]>only | list of string | - | The user collections to create seeding data for. If not provided, all available collections will be seeded. |
None.
Use dismissMessage
to dismiss messages that are marked with isDismissible
equals true
. Using this function on non-dismissible functions results in an error. Clients may call this function either on deletion of the user (by displaying an x-mark tapped by the user to specifically remove this message) or on perform of the message (i.e. on tap of the message, if the action could successfully be decoded and performed).
Admins may dismiss messages for any user, otherwise users may only dismiss their own messages.
Property | Type | Values | Comments |
---|---|---|---|
userId | optional string | - | The userId of the user whose message you are dismissing. May be omitted for users dismissing their own messages. |
messageId | string | - | The id of the message to dismiss. This message needs to have isDismissible set to true . |
didPerformAction | optional boolean | - | Whether the message action has actually been performed. Depending on this value, the server may decide to actually dismiss the message or not. |
None.
Use enrollUser
to enroll a user, i.e. use an invitation code to create a full user account.
Any authenticated, non-enrolled user may use this function.
Property | Type | Values | Comments |
---|---|---|---|
invitationCode | string | - | The invitation code provided to the user by the organization. It needs to be between 6-12 characters long and only use uppercase latin characters and arabic digits. |
None.
exportHealthSummary
creates a health summary PDF and returns its data.
This function may be called by admins (for any patient), owners/clinicians (for patients of the same organization), or patients (for themselves).
Property | Type | Values | Comments |
---|---|---|---|
userId | string | - | The patient's user id. Needs to be specified, even if a patient is requesting the health summary for themselves. |
language | optional string | e.g. 'en-US' | See LocalizedText for specification. |
weightUnit | optional string | e.g. '[lb_av]' | A loinc code for the weight unit to be used during generation of the health summary PDF |
Property | Type | Values | Comments |
---|---|---|---|
content | string | - | Base64-encoded string of the PDF data. |
Use registerDevice
to register different client devices for receival of push notifications. Call this function on each fresh start of the app or whenever the push notification token (or any of the remaining data, including language/region settings) may have changed.
Any user can register devices for their own account. In the foreseeable future, the function is only relevant for patients though.
If a notification token could not be generated on the device (e.g. due to missing permissions), simply do not call this function rather than creating a device without token. For the remaining inputs, please provide all values that are available.
Property | Type | Values | Comments |
---|---|---|---|
notificationToken | string | - | The notification token to be used for sending push notifications. This may either be an APNS token for iOS devices or a FCM registration token for Android. |
platform | string | 'iOS' or 'Android' | The platform of the device. |
osVersion | optional string | '15.4.3' | The OS version of the device using semantic versioning separated by dots. Minor and patch version may be omitted. |
appVersion | optional string | '1.0.1' | The app version as shown in the App Store or Play Store using semantic versioning. Minor and patch version may be omitted. |
appBuild | optional string | '43' | The app build version as used internally to identify individual builds within the same marketing version (i.e. the one shown in App Store / Play Store). |
language | optional string | 'en-US' | The language and region setting as specified for LocalizedText . |
timeZone | optional string | e.g. "America/Los_Angeles" | The value needs to correspond to an identifier from TZDB. It must not be an offset to UTC/GMT, since that wouldn't work well with daylight-savings (even if there is no daylight-savings time at that location). Also, don't use common abbreviations like PST, PDT, CEST, etc (they may be ambiguous, e.g. CST). If the timeZone is unknown, then "America/Los_Angeles" should be used. |
None.
Use unregisterDevice
to remove a notification token associated to a user account. This will only remove it from the authenticated user.
Any user can unregister devices for their own account. In the foreseeable future, the function is only relevant for patients though.
If a notification token could not be generated on the device (e.g. due to missing permissions), simply do not call this function rather than creating a device without token. For the remaining inputs, please provide all values that are available.
Property | Type | Values | Comments |
---|---|---|---|
notificationToken | string | - | The notification token to be used for sending push notifications. This may either be an APNS token for iOS devices or a FCM registration token for Android. |
platform | string | 'iOS' or 'Android' | The platform of the device. |
None.
Use updateStaticData
to update statically present data in Firestore. This function may either be called when the configuration of the static data has changed or when dynamically generated content needs an update (e.g. medication data is generated based on the RxNorm API).
This function may only be called by admins. On emulators, this function can easily be triggered without authentication using an HTTP POST request.
Property | Type | Values | Comments |
---|---|---|---|
only | optional list of string | e.g. ["videoSections","medications"] | The static components to update. If omitted, all components will be updated. |
cachingStrategy | optional string | e.g. "expectCache" | Depending on this property static data may simply be updated in Firestore based on cached data or completely generated from scratch and not relying on cached information. |
None.
To perform certain queries, ENGAGE-HF requires indexes on different properties. The following indexes need to be created:
Type | Reference | Properties | Usage |
---|---|---|---|
Composite | collection:users | organization:asc,type:asc,name:asc | Querying users on the Web dashboard |
Composite | collection:invitations | user.organization:asc,user.type:asc,name:asc | Querying invitations on the Web dashboard |
Single | group:appointments | start:asc | Querying appointments across all users (for appointment messages) |
Single | group:devices | notificationToken:asc | Querying devices across all users (for deleting existing notification tokens assigned to other users) |
- See resources/algorithms for diagrams describing the different algorithms for medication recommendations.
- For definitions relevant for the setup of static data, including questionnaires, medication classes, medications and videoSections, have a look at functions/data.
A user usually has one of these roles assigned (some combinations are technically allowed, but may be ignored, e.g. owner+clinician).
Role | Scope | Rights | Source of Truth |
---|---|---|---|
Admin | Everything | R/W | User type is admin . |
Owner | In organization | R/W of users, R/W of organization | User type is owner . |
Clinician | In organization | R/W of users | User type is clinician . |
Patient | Own data | R/W of users/$userId$ incl patient-specific collections |
User type is patient . |
User | Own data | R/W of users/$userId$ |
auth has same userId |
For more detail, please consult the Firestore rules defined in firestore.rules.
This project is licensed under the MIT License. See Licenses for more information.
This project is developed as part of the Stanford Mussallem Center for Biodesign at Stanford University. See CONTRIBUTORS.md for a full list of all contributors.