...
For the first time the server will need to have knowledge of the participant’s time and time zone;
The reports are (relatively) expensive to calculate, in bulk they are not likely to change frequently, and they will need to be the building block for arm and study summary reports, so they will be read often, write seldom and will need to be aggressively cached.
Adherence Event Day Report
Basic structure of adherence record (per participant) is:
durationInDays (the highest endDay value calculated by the timeline, which can be lower that the schedule duration, but the schedule duration will cap any open-ended sequences);
Enum studyAdherence (compliant, not in compliance, some measure of this);
Map<Stream,List<SessionAdherence>> adherenceTimeline;
There’s nothing in this about event sequences that haven’t happened because they haven’t been triggered by an event. We might need to encode, in a schedule, what is desired in terms of event generation.
There’s nothing about individual sessions or assessments being optional. There’s no easy way to determine adherence for persistent time windows, so we’ll assume that performing such a window one time is compliant…maybe.
Stream
The timeline describes parts of scheduling that can be repeated, if the triggering events can change over time. Study bursts are also an example of repeating parts of scheduling. Each of these things represents a separate stream.
label (the session name, probably)
startEventId
eventTimestamp
session window GUID (each time window generates one stream)
SessionAdherence (might be possible to edit the state if coordinator knows state better). In visual designs, all time windows are grouped under each session (in our server model, each window creates a separate stream of activities…but we would want a set of tests). So we might have a set of WindowAdherence records, as below, in one session adherence container (not sure there’s much in the container).
state
- not yet available NOT_YET_AVAILABLE
- available but unstarted UNSTARTED
- started STARTED
- successfully completed COMPLETED
- expired unfinished ABANDONED
- expired never started IGNORED/NONCOMPLIANTinstanceGuid
sessionGuid
startDay
endDay
startTime
expiration
SessionAdherence needs to boil down the completed state of the session based on the timestamps, I think. User would be out of compliance if some of their states are expired. Should probably be possible to calculate these records and then store them so Bridge can deliver them. So a summary of the state of the record should probably also be saved for efficient querying.
API
Method | Path |
---|---|
GET | /v5/studies/{studyId}/participants/{userId|self}/adherence/eventday |
GET | /v5/studies/{studyId}/participants/adherence/eventday |
Given Lynn’s designs, here’s what I think the JSON would look like. (I have prototyped this; it’ll work though it involves loading everything about the user.) The single biggest thing about her designs is that she regroups the time window event streams, into each encompassing session instance. Also note that in the view below, we return the entire timeline for the user, but it is possible to calculate the active components or adherence percentages from the time window completion states. Finally, this view assumes current timestamps only; such a view could be constructed for all timestamps, and I don’t know how that would be organized.
Code Block | ||
---|---|---|
| ||
[
{
"startEventId":"study_burst:ClinicVisit:01",
"eventTimestamp":"2021-10-27T19:00:00.000Z",
"entries":{
"1":[
{
"sessionGuid":"vZBHBVv_H2_1TBbELF48czjS",
"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",
"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",
"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",
"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"
}
] |
In the second API, which we probably need to calculate with a worker, only entries with active elements would be returned. That can be more than one record for a given participant, and we might simply implement a filter on this server process to retrieve the required records. However, we may need caching/persistence because this only changes when an adherence record is submitted for a user.
Caching individual participant adherence reports
...