Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

EventStream Report (all adherence data for one participant)

This report is intended to support the following type of adherence chart:

...

The report is calculated against the caller’s clientTimeZone value (if they have a value set), or else it is calculated against the timeZone value of the Study (to determine the current date). This is what the JSON would like like:

Code Block
languagejson
{
  "activeOnly": false,
  "timestamp": "2021-11-23T22:00:31.699Z",
  "clientTimeZone": "America/Los_Angeles",
  "adherencePercent": 25,
  "streams": [
    {
      "startEventId": "custom:event1",
      "eventTimestamp": "2021-11-21T20:00:00.000Z",
      "daysSinceEvent": 2,
      "byDayEntries": {
        "0": [
          {
            "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
            "sessionLabel": "Session #2",
            "sessionSymbol": "2",
            "startDay": 0,
            "startDate": "2021-11-21",
            "timeWindows": [
              {
                "sessionInstanceGuid": "bDBVV02XrjrOG8NgBYSYFg",
                "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
                "state": "completed",
                "endDay": 0,
                "endDate": "2021-11-21",
                "type": "EventStreamWindow"
              }
            ],
            "type": "EventStreamDay"
          }
        ],
        "1": [
          {
            "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
            "sessionLabel": "Session #2",
            "sessionSymbol": "2",
            "startDay": 1,
            "startDate": "2021-11-22",
            "timeWindows": [
              {
                "sessionInstanceGuid": "u-cc8Ou4xqUPVnht4oh-bw",
                "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
                "state": "expired",
                "endDay": 1,
                "endDate": "2021-11-22",
                "type": "EventStreamWindow"
              }
            ],
            "type": "EventStreamDay"
          }
        ]
      },
      "type": "EventStream"
    },
    {
      "startEventId": "custom:event2",
      "eventTimestamp": "2021-11-15T20:00:00.000Z",
      "daysSinceEvent": 8,
      "byDayEntries": {
        "0": [
          {
            "sessionGuid": "z_jb4p2Lr9Q56z8AwiYNieqw",
            "sessionLabel": "Session #3",
            "sessionSymbol": "3",
            "startDay": 0,
            "startDate": "2021-11-15",
            "timeWindows": [
              {
                "sessionInstanceGuid": "KAxvwhsX6jSVl89a3-gdKw",
                "timeWindowGuid": "gF6hy-UiipJLXqe7F_yK-wQc",
                "state": "expired",
                "endDay": 2,
                "endDate": "2021-11-17",
                "type": "EventStreamWindow"
              }
            ],
            "type": "EventStreamDay"
          }
        ],
        "3": [
          {
            "sessionGuid": "z_jb4p2Lr9Q56z8AwiYNieqw",
            "sessionLabel": "Session #3",
            "sessionSymbol": "3",
            "startDay": 3,
            "startDate": "2021-11-18",
            "timeWindows": [
              {
                "sessionInstanceGuid": "nQnWs_4ECvLtY4K1gYk67g",
                "timeWindowGuid": "gF6hy-UiipJLXqe7F_yK-wQc",
                "state": "expired",
                "endDay": 5,
                "endDate": "2021-11-20",
                "type": "EventStreamWindow"
              }
            ],
            "type": "EventStreamDay"
          }
        ]
      },
      "type": "EventStream"
    },
    {
      "startEventId": "study_burst:main-sequence:01",
      "eventTimestamp": "2021-11-21T20:00:00.000Z",
      "daysSinceEvent": 2,
      "studyBurstId": "main-sequence",
      "studyBurstNum": 1,
      "byDayEntries": {
        "0": [
          {
            "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
            "sessionLabel": "Session #1",
            "sessionSymbol": "1",
            "startDay": 0,
            "startDate": "2021-11-21",
            "timeWindows": [
              {
                "sessionInstanceGuid": "1aCbUaFYkixIsIJBf9WGpg",
                "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
                "state": "expired",
                "endDay": 0,
                "endDate": "2021-11-21",
                "type": "EventStreamWindow"
              },
              {
                "sessionInstanceGuid": "yQnubrShfYMY9ZzOE3zw3Q",
                "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
                "state": "expired",
                "endDay": 0,
                "endDate": "2021-11-21",
                "type": "EventStreamWindow"
              }
            ],
            "type": "EventStreamDay"
          }
        ]
      },
      "type": "EventStream"
    }
  ],
  "type": "EventStreamAdherenceReport"
}

EventStreamWindow includes the state of that session (which is the basis for adherence calculation). This adherence report could be displayed on a calendar since it is specific to a user and the server must know all the calendrical values to calculate it (and it's the only report that can take a timestamp to indicate the time at which the report should be calculated, and it is calculated in the user’s local time if possible).

The completion states are:

...

This report always returns seven days of adherence records for a given user. The report calculates this information by finding the “week since event N” for every event and every session (it will be the week that has the day that falls on “today.”) This timestamp won’t be adjustable since every timestamp would require a recalculation of all these reports—so it’ll have to be a date on the server, possibly adjustable per study to a different time zone. “today” as measured in the user’s local time zone). For performance reasons, we will not calculate this week outside of the “current week” so it’s not possible to move forward or backward from this view (at least initially).

Note that the the individual sessions listed in this report are not lined up by calendar date (unless they were triggered by the same event). The structure of this report would be as follows:

Code Block
languagejson
{
  "participant": {
    "firstName": "A-chan",
    "email": "alx.dark+achan@sagebase.org",
    "externalId": "asdfasdf",
    "identifier": "GqYpNUWolebxS2eQudF1hc-a",
    "type": "AccountRef"
  },
  "timestamprequestTimestamp": "2021-11-23T2223T21:03:21.356Z",
  "weeklyAdherencePercentclientTimeZone": 33"America/Los_Angeles",
  "byDayEntriescreatedOn": {
 "2021-11-23T22:03:21.356Z",
  "0weeklyAdherencePercent": [33,
  // nextActivity, if byDayEntries {is empty
   "byDayEntries": {
    "0": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-21",
        "timeWindows": [
          {
            "sessionInstanceGuid": "bDBVV02XrjrOG8NgBYSYFg",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "completed",
            "endDate": "2021-11-21",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-21",
        "timeWindows": [
          {
            "sessionInstanceGuid": "1aCbUaFYkixIsIJBf9WGpg",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "expired",
            "endDate": "2021-11-21",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "yQnubrShfYMY9ZzOE3zw3Q",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "expired",
            "endDate": "2021-11-21",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "1": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-22",
        "timeWindows": [
          {
            "sessionInstanceGuid": "u-cc8Ou4xqUPVnht4oh-bw",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "expired",
            "endDate": "2021-11-22",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-22",
        "timeWindows": [
          {
            "sessionInstanceGuid": "7HaIdOehYJrJk3VZcGeNxg",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "completed",
            "endDate": "2021-11-22",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "R0I7n1fEeIR88fYv6iVPTg",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "expired",
            "endDate": "2021-11-22",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "2": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-23",
        "timeWindows": [
          {
            "sessionInstanceGuid": "s7HuTxm4E-ffhfiddVFkXQ",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "started",
            "endDate": "2021-11-23",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "z_jb4p2Lr9Q56z8AwiYNieqw",
        "sessionLabel": "Session #3",
        "sessionSymbol": "3",
        "week": 2,
        "startDate": "2021-11-24",
        "timeWindows": [
          {
            "sessionInstanceGuid": "WHE_gHE71tk8qFERatcruA",
            "timeWindowGuid": "gF6hy-UiipJLXqe7F_yK-wQc",
            "state": "not_yet_available",
            "endDate": "2021-11-26",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-23",
        "timeWindows": [
          {
            "sessionInstanceGuid": "SgTohgnPkGjB9eE73QRXEA",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "completed",
            "endDate": "2021-11-23",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "ezwrtaRiJd5Bfa3w5Xxffw",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "unstarted",
            "endDate": "2021-11-23",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "3": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-24",
        "timeWindows": [
          {
            "sessionInstanceGuid": "u78_-kFdyNnKt6wbUmSXcA",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "not_yet_available",
            "endDate": "2021-11-24",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-24",
        "timeWindows": [
          {
            "sessionInstanceGuid": "mcpv_-sW1vXfk4Vyi5dwgg",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "not_yet_available",
            "endDate": "2021-11-24",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "t4AEs7UzOjXYACSOO1REPA",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "not_yet_available",
            "endDate": "2021-11-24",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "4": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-25",
        "timeWindows": [
          {
            "sessionInstanceGuid": "tJxcgcQK40X6jL71MORb3g",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "not_yet_available",
            "endDate": "2021-11-25",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-25",
        "timeWindows": [
          {
            "sessionInstanceGuid": "22nOvDMhYIE7btX6X8zlHw",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "not_yet_available",
            "endDate": "2021-11-25",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "H2EG1SV-tF_s8dmKWwvtOQ",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "not_yet_available",
            "endDate": "2021-11-25",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "5": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-26",
        "timeWindows": [
          {
            "sessionInstanceGuid": "-OoNi3vhifRUqN6X9WTMCg",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "not_yet_available",
            "endDate": "2021-11-26",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "z_jb4p2Lr9Q56z8AwiYNieqw",
        "sessionLabel": "Session #3",
        "sessionSymbol": "3",
        "week": 2,
        "startDate": "2021-11-27",
        "timeWindows": [
          {
            "sessionInstanceGuid": "2O-jPnpWOYZLx0VjBvvO8g",
            "timeWindowGuid": "gF6hy-UiipJLXqe7F_yK-wQc",
            "state": "not_yet_available",
            "endDate": "2021-11-29",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-26",
        "timeWindows": [
          {
            "sessionInstanceGuid": "JfyiAbHZ-nlmTZui_N19Tg",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "not_yet_available",
            "endDate": "2021-11-26",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "g6kRO-JZgt0lGghDT8v37A",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "not_yet_available",
            "endDate": "2021-11-26",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ],
    "6": [
      {
        "sessionGuid": "eRLgI5gfe1kef_XRZDfdFU9I",
        "sessionLabel": "Session #2",
        "sessionSymbol": "2",
        "week": 1,
        "startDate": "2021-11-27",
        "timeWindows": [
          {
            "sessionInstanceGuid": "_-1AlFrrN9IfA5tK-OYvag",
            "timeWindowGuid": "KZ1piANVdeD-r8PCHL2bviLh",
            "state": "not_yet_available",
            "endDate": "2021-11-27",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      },
      {
        "sessionGuid": "LcWpQFKaGY5FSQ0LT4tnvdO7",
        "sessionLabel": "Session #1",
        "sessionSymbol": "1",
        "week": 1,
        "studyBurstId": "main-sequence",
        "studyBurstNum": 1,
        "startDate": "2021-11-27",
        "timeWindows": [
          {
            "sessionInstanceGuid": "IUBXsO4ioFgJfi8GA9NSxw",
            "timeWindowGuid": "GNp94CnfTTtR-s0OzrFeftrh",
            "state": "not_yet_available",
            "endDate": "2021-11-27",
            "type": "EventStreamWindow"
          },
          {
            "sessionInstanceGuid": "WdRjHkDWoqmQx4vxRuCVeg",
            "timeWindowGuid": "aRaHNKIY0yKgOl5CLuA3ZDHJ",
            "state": "not_yet_available",
            "endDate": "2021-11-27",
            "type": "EventStreamWindow"
          }
        ],
        "type": "EventStreamDay"
      }
    ]
  },
  "type": "WeeklyAdherenceReport"
}

...

Code Block
languagesql
CREATE TABLE `WeeklyAdherenceReports` (
  `appId` varchar(255) NOT NULL,
  `studyId` varchar(255) NOT NULL,
  `userId` varchar(255) NOT NULL,
  `clientTimestamp``requestTimestamp` bigint(20) unsigned NOT NULL,
  `createdOn` bigint(20) unsigned NOT NULL,
  `labels` text NOT NULL,
  `weeklyAdherencePercent` int(3) unsigned NOT NULL,
  `reportData` mediumtext NOT NULL,
  PRIMARY KEY (`appId`, `studyId`, `userId`),
  CONSTRAINT `WeeklyAdherenceReports-Study-Constraint` 
      FOREIGN KEY (`studyId`, `appId`) 
      REFERENCES `Substudies` (`id`, `studyId`) ON DELETE CASCADE,
  CONSTRAINT `WeeklyAdherenceReports-Account-Constraint` 
      FOREIGN KEY (`userId`) 
      REFERENCES `Accounts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

...

Weekly Adherence Reports for Study

This API is intended to support the following kind of adherence overview for the study:

...

The final API will allow an admin to retrieve a paged set of weekly adherence records for all participants in the study. This retrieves the records from the database and does not calculate them. The paging search criteria should be the same as the study participants search API. In addition to the search arguments in the /v5/studies/{studyId}/participants/search API, it should be possible to filter and or sort on the following fields:

...

I would like to reuse the search code for participants, but it may be necessary to join tables in a way where that can’t be done, while still maintaining the desired paging logic. To start, it’s probably possible to just search and sort by adherence and labels.

Protocol adherence and notifications (worker process)

Since all the reports give an adherence percentage (whole study or one week), we can use this to sort records by adherence in the weekly view, or to generate a message to administrators as part of the worker process that will be run nightly to update the weekly reports. Any necessary configuration for this can be added to the Study model (in particular, at what percentage of adherence should we notify administrators that a participant is out of adherence).

A worker process would go through all apps nightly, looking for apps with studies that have schedules, and then for each of these studies, it would iterate through all participants in the study, calling the weekly adherence API endpoint for that participant. This would generate and persist their adherence report. It can also generate adherence messages, if need be.If that’s too much, we can set a flag on apps or studies to do this caching or notcould run hourly, querying Bridge for any studies that had a time zone such that the local time of the study was at a specific time (e.g. 4am). If that study had a schedule, all the participants would be retrieved and the weekly adherence report would be generated for them. Since these reports are being generated on the server’s time, it’s possible in unusual scenarios that this time would give odd results for a user in a far off time zone…if so we can possibly run reports for a study more frequently than every 24 hours (but we needn’t start by implementing to prevent this).

If the worker hit certain criteria, it could also send a message warning about a participant being out of adherence.

Adherence Service

The following methods will be added to the AdherenceService:

...

Code Block
languagejava
public class Subscription {
  String appId;
  String studyId;
  MessageType messageType;
  String recipientId;
}

// As submitted
public class Message {
  String studyId;
  MessageType messageType;
  String text;
}

// As persisted, one record per subscription
public class Message {
  String guid;
  String appId;
  String studyId;
  String userId;
  MessageType messageType;
  String text;
  DateTime createdOn;
  boolean read;
  String readBy;
}

Additional proposed work items

  • Allow study burst and custom event IDs to have spaces, essentially allowing them to be human-readable labels like “Study Burst” or “Clinic Visit.” Right now we prevent the obvious labels from being used but this becomes difficult when we’re going to allow people to search by labels that include these identifiers.

  • Study should add a studyTimeZone field. We may also want to add an adherenceThresholdPercent in anticipation of messaging.