...
Method | Path | Description |
---|---|---|
GET | /v5/studies/{studyId}/participants/{userId|self}/adherence/eventday?activeOnly=true|false | activeOnly = show only currently available days. This can be expanded to show days before or after, etc. as useful to clients. |
The JSON looks as follows (note that we can add whatever further information we need based on the UI, but the UI— though ultimately, it may make better sense to look up session and assessment metadata in the timeline). The existing designs are currently minimal, with all windows just being represented by a symbol colored to show its current adherence state):
Code Block | ||
---|---|---|
| ||
[ { "startEventId":"study_burst:ClinicVisit:01", "eventTimestamp":"2021-10-27T19:00:00.000Z", "entries":{ "1":[ { "sessionGuid":"vZBHBVv_H2_1TBbELF48czjS", "label": "Session #1", "symbol": "circle", "timeWindows":[ { "sessionInstanceGuid":"ePcCf6VmfIiVuU0ckdBeRw", "timeWindowGuid":"sUaNAasy_LiT3_IYa1Fx_dSv", "state":"not_yet_available", "type":"EventDayWindow" }, { "sessionInstanceGuid":"DB13D4mO72j6S-g7PIkI2Q", "timeWindowGuid":"Bw6rAAeG6zotqes4cLSgKjh5", "state":"not_yet_available", "type":"EventDayWindow" } ], "type":"EventDay" } ], "2":[ { "sessionGuid":"vZBHBVv_H2_1TBbELF48czjS", "label": "Session #1", "symbol": "circle", "timeWindows":[ { "sessionInstanceGuid":"wvEV4fJZQ0nfgY-TN2LekA", "timeWindowGuid":"sUaNAasy_LiT3_IYa1Fx_dSv", "state":"not_yet_available", "type":"EventDayWindow" }, { "sessionInstanceGuid":"IHDTSoj552vGDv1Qt7nXkg", "timeWindowGuid":"Bw6rAAeG6zotqes4cLSgKjh5", "state":"not_yet_available", "type":"EventDayWindow" } ], "type":"EventDay" } ] }, "type":"EventDayAdherenceReport" }, { "startEventId":"study_burst:ClinicVisit:02", "eventTimestamp":"2021-11-16T19:00:00.000Z", "entries":{ "1":[ { "sessionGuid":"vZBHBVv_H2_1TBbELF48czjS", "label": "Session #1", "symbol": "circle", "timeWindows":[ { "sessionInstanceGuid":"zk7X4dQCy7Nvnuo2PcnSCA", "timeWindowGuid":"sUaNAasy_LiT3_IYa1Fx_dSv", "state":"not_yet_available", "type":"EventDayWindow" }, { "sessionInstanceGuid":"rMRne-cbwIN5mkGZLymxzg", "timeWindowGuid":"Bw6rAAeG6zotqes4cLSgKjh5", "state":"not_yet_available", "type":"EventDayWindow" } ], "type":"EventDay" } ], "2":[ { "sessionGuid":"vZBHBVv_H2_1TBbELF48czjS", "label": "Session #1", "symbol": "circle", "timeWindows":[ { "sessionInstanceGuid":"QXM1cO6yb0gSPWzRwRD8eA", "timeWindowGuid":"sUaNAasy_LiT3_IYa1Fx_dSv", "state":"not_yet_available", "type":"EventDayWindow" }, { "sessionInstanceGuid":"hCXFevxbBnpaUYjH212dsQ", "timeWindowGuid":"Bw6rAAeG6zotqes4cLSgKjh5", "state":"not_yet_available", "type":"EventDayWindow" } ], "type":"EventDay" } ] }, "type":"EventDayAdherenceReport" } ] |
All sessions in the timeline are grouped in this view by the event that triggers them, and then the number of days since that event. All potential events in the schedule are included in this report whether they exist for the user or not (we don’t currently have a way to say “count this part of the schedule if the event exist for the user, but don’t count it if the event doesn’t exist for the user). Then the actual “days since each event” are calculated to determine what the state of these assessments. The states are:
State | Description | Adherence |
---|---|---|
not_yet_available | Participant should not have seen or started this assessment. It’s in the future. | N/A |
unstarted | Participant should see the assessment (they are being asked to do it now), but they have not started it. | unknown |
started | Participant has started the assessment. | unknown |
completed | Participant has completed the assessment before it expired and was removed from the UI. | compliant |
abandoned | Participant started the assessment but it expired before they finished it. | noncompliant |
expired | Participant did not start the assessment ; and it is now expired. | noncompliant |
...
These reports are not easily cacheable because the states depend entirely on the current time . However, they are at a day level of resolution, so it we should be possible to cache them for less than a day. The cache can be invalidated when event timestamps or adherence records are updated.There are options for where to store these. If we don’t need a historical record of compliance, we could use Redis. If we want a historical record of compliance, we could write these as participant reports, or we could write them back to the databaseof the request. A single report takes about 1/3 of a second, but paginated lists of user would be prohibitively expensive to calculate. We may need to calculate these nightly and store them with an additional timestamp, so they can be retrieved in paginated form. As a result, this higher-level view or any reports generated from it will not be “real time,” but individuals could be explored or refreshed to see more up-to-date information.
In past work I have created “sweepers” for things like sporting event games that just endlessly loop and refresh caches. This allows you to balance freshness of the data with resource utilization.
This probably means that we will have a flag on App to enable this nightly report generation for specific apps. We have reports that count uploads and sign ins, but we hard-coded exclusion lists so they don’t run in most of our Apps. We should be able to toggle these reports through the Bridge APIs.
Protocol adherence
There is definitely a lot of possible ways we could measure adherence. However it can be measured as a proportion of compliant to non-compliant scheduled assessment states. We could then set a threshold for this (ie. warn administrators if there are any non-compliant assessments, vs. setting a proportion threshold that would need to be hit before notifying administrators). This can be calculated as part of the event day adherence report.
...