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 /v3/participants/{userId}/demographics

Save/overwrite all demographics for a user

App-level, posted on the user’s behalf (by app admin)

POST /v3/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 /v3/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 /v3/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 /v3/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 /v3/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 /v3/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 /v3/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": [
    {
      "children": [
        {
          "identifier": <string> (category name),
          "answerType": { (optional)
              "unit": <string>
          },
          "value": <single value, any type OR array of single value, any type OR object with fields of single value, any type>
        }
      ]
    }
  ]
}

Although other fields may be included in the normal assessment model, all other fields are discarded for demographics use.

Example:

{
  "stepHistory": [
    {
      "children": [
        {
          "identifier": "height",
          "answerType": {
              "unit": "cm"
          },
          "value": 72.0
        },
        {
          "identifier": "race",
          "value": [
            "Asian",
            "Native Hawaiian or Other Pacific Islander"
          ]
        }
      ]
    }
  ]
}

Internal model

SQL:

CREATE TABLE IF NOT EXISTS `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 IF NOT EXISTS `Demographics` (
    `id` varchar(60) NOT NULL,
    `demographicUserId` varchar(60) NOT NULL,
    `categoryName` varchar(255) 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 IF NOT EXISTS `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.

App-level demographics are represented with a null studyId.