Adds functionality to store demographic information on the server. See /wiki/spaces/MTB/pages/1934819724 and Demographic Survey Categories (somewhat outdated).
Type Checking
Currently, there is no type checking on demographic answer values, so all values are stored as strings. Type checking is a V2 goal.
App-Level vs. Study-Level
Routes are available for both app-level and study-level demographics. The idea is that some basic demographics might be collected during app onboarding which is not associated with a particular study, and then additional demographics data might be collected by a particular study within the app.
Routes
Saving
POST /v5/studies/{studyId}/participants/{userId}/demographics
Save/overwrite all demographics for a user
Study-level, posted on the user’s behalf (by researcher, study coordinator)
POST /v5/studies/{studyId}/participants/self/demographics
Save/overwrite all demographics for a user
Study-level, posted by the user
POST /v1/apps/self/participants/{userId}/demographics
Save/overwrite all demographics for a user
App-level, posted on the user’s behalf (by app admin)
POST /v1/apps/self/participants/self/demographics
Save/overwrite all demographics for a user
App-level, posted by the user
POST /v5/studies/{studyId}/participants/{userId}/demographics/assessment
Save/overwrite all demographics for a user
Study-level, posted on the user’s behalf (by researcher, study coordinator)
Uses the assessment JSON model
POST /v5/studies/{studyId}/participants/self/demographics/assessment
Save/overwrite all demographics for a user
Study-level, posted by the user
Uses the assessment JSON model
POST /v1/apps/self/participants/{userId}/demographics/assessment
Save/overwrite all demographics for a user
App-level, posted on the user’s behalf (by app admin)
Uses the assessment JSON model
POST /v1/apps/self/participants/self/demographics/assessment
Save/overwrite all demographics for a user
App-level, posted by the user
Uses the assessment JSON model
Deleting
DELETE /v5/studies/{studyId}/participants/{userId}/demographics/{demographicId}
Deletes a specific demographic (single category) for a particular user
Study-level, done by researcher or study coordinator
DELETE /v1/apps/self/participants/{userId}/demographics/{demographicId}
Deletes a specific demographic (single category) for a particular user
App-level, done by app admin
DELETE /v5/studies/{studyId}/participants/{userId}/demographics
Deletes all of a user’s demographics
Study-level, done by researcher or study coordinator
DELETE /v1/apps/self/participants/{userId}/demographics
Deletes all of a user’s demographics
App-level, done by app admin
Fetching
GET /v5/studies/{studyId}/participants/{userId}/demographics
Fetches all demographics for a user
Study-level, done by researcher/study-coordinator
GET /v1/apps/self/participants/{userId}/demographics
Fetches all demographics for a user
App-level, done by app admin
GET /v5/studies/{studyId}/participants/demographics
Fetches all study-level demographics for all users within a study
Study-level, done by researcher/study-coordinator
GET /v1/apps/self/participants/demographics
Fetches all app-level demographics for all users within an app
App-level, done by app admin
JSON Format
Unless otherwise stated, routes use the following schema (for a single user):
{ "userId": (read only) <string>, "demographics": { (category name): { "id": (guid) (read only) <string>, "multipleSelect": <bool>, "values": [ <any> ] "units": <string> }, ... } }
Example:
{ "userId": "userId1", "demographics": { "height": { "id": "guid1", "multipleSelect": false, "values": [ 72.0 ] "units": "m" }, "race": { "id": "guid2", "multipleSelect": true, "values": [ "Asian", "Native Hawaiian or Other Pacific Islander" ] } } }
Certain routes use an assessment response model for demographic input:
{ "stepHistory": [ { "identifier": <string> (category name), "answerType": { "type": <string> (this field is only checked to see if it contains "array") }, "value": <array or single value, any type> } ] }
Although other fields may be included in the normal assessment model, all other fields are discarded for demographics use. “type” is only used to determine whether to deserialize “value” as an array or single value.
Example:
{ "stepHistory": [ { "identifier": "height", "answerType": { "type": "float" }, "value": 72.0 }, { "identifier": "race", "answerType": { "type": "array" }, "value": [ "Asian", "Native Hawaiian or Other Pacific Islander" ] } ] }
Internal model
SQL:
CREATE TABLE `DemographicsUsers` ( `id` varchar(60) NOT NULL, `studyId` varchar(60) NULL, `appId` varchar(60) NOT NULL, `userId` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY (`studyId`, `appId`, `userId`), CONSTRAINT `DemographicUser-Account-Constraint` FOREIGN KEY (`userId`) REFERENCES `Accounts` (`id`) ON DELETE CASCADE, CONSTRAINT `DemographicUser-Study-Constraint` FOREIGN KEY (`studyId`, `appId`) REFERENCES `Substudies` (`id`, `studyId`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE TABLE `Demographics` ( `id` varchar(60) NOT NULL, `demographicUserId` varchar(60) NOT NULL, `categoryName` varchar(768) NOT NULL, `multipleSelect` boolean NOT NULL, `units` varchar(512) NULL, PRIMARY KEY (`id`), UNIQUE KEY (`demographicUserId`, `categoryName`), CONSTRAINT `Demographic-DemographicUser-Constraint` FOREIGN KEY (`demographicUserId`) REFERENCES `DemographicsUsers` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; CREATE TABLE `DemographicsValues` ( `demographicId` varchar(60) NOT NULL, `value` varchar(1024) NOT NULL, CONSTRAINT `DemographicValue-Demographic-Constraint` FOREIGN KEY (`demographicId`) REFERENCES `Demographics` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
3 tables are used because each table has a one-to-many relationship with the table below it, both intuitively and in SQL, and this nested format is much simpler with multiple tables. It also allows convenient grouping by userId for a better JSON format.
DemographicsUsers
: maps to Java classDemographicUser
Contains all demographics for a single user
One-to-many with
Demographics
Demographics
: maps to Java classDemographic
Contains all values for a single demographic category for a single user
One-to-many with
DemographicsValues
DemographicsValues
: maps to Java classDemographicValue
Contains a single value in a demographic category
App-level demographics are represented with a null studyId.