From 652b118856f3b9383a42e21bf3e656e858c041e7 Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Fri, 2 Feb 2024 04:11:47 -0800 Subject: [PATCH 1/8] Preliminary qualtrics integration --- wearipedia/devices/qualtrics/__init__.py | 1 + wearipedia/devices/qualtrics/qualtrics.py | 136 ++++++++++++ .../devices/qualtrics/qualtrics_fetch.py | 173 +++++++++++++++ wearipedia/devices/qualtrics/qualtrics_gen.py | 210 ++++++++++++++++++ 4 files changed, 520 insertions(+) create mode 100644 wearipedia/devices/qualtrics/__init__.py create mode 100644 wearipedia/devices/qualtrics/qualtrics.py create mode 100644 wearipedia/devices/qualtrics/qualtrics_fetch.py create mode 100644 wearipedia/devices/qualtrics/qualtrics_gen.py diff --git a/wearipedia/devices/qualtrics/__init__.py b/wearipedia/devices/qualtrics/__init__.py new file mode 100644 index 00000000..32920181 --- /dev/null +++ b/wearipedia/devices/qualtrics/__init__.py @@ -0,0 +1 @@ +from .myfitnesspal import * diff --git a/wearipedia/devices/qualtrics/qualtrics.py b/wearipedia/devices/qualtrics/qualtrics.py new file mode 100644 index 00000000..b01b1b15 --- /dev/null +++ b/wearipedia/devices/qualtrics/qualtrics.py @@ -0,0 +1,136 @@ +import os +import pickle +from datetime import datetime + +import myfitnesspal + +from ...devices.device import BaseDevice +from ...utils import seed_everything +from .myfitnesspal_fetch import * +from .myfitnesspal_synthetic import * + +class_name = "MyFitnessPal" + + +class MyFitnessPal(BaseDevice): + + """This device allows you to work with data from the `MyFitnessPal `_ app. + Available datatypes for this device are: + + * `goals`: a list that contains goals data for each day + + * `daily_summary`: a list that contains daily summary data for each day + + * `exercises_cardio`: a list that contains cardio exercises data for each day + + * `exercises_strength`: a list that contains strength exercises data for each day + + * `lunch`: a list that contains lunch data for each day + + * `breakfast`: a list that contains breakfast data for each day + + * `dinner`: a list that contains dinner data for each day + + * `snacks`: a list that contains snacks data for each day + + :param seed: random seed for synthetic data generation, defaults to 0 + :type seed: int, optional + :param synthetic_start_date: start date for synthetic data generation, defaults to "2022-03-01" + :type synthetic_start_date: str, optional + :param synthetic_end_date: end date for synthetic data generation, defaults to "2022-06-17" + :type synthetic_end_date: str, optional + :param use_cache: decide whether to cache the credentials, defaults to True + :type use_cache: bool, optional + """ + + def __init__(self, seed=0, start_date="2022-03-01", end_date="2022-06-17"): + params = { + "seed": seed, + "start_date": str(start_date), + "end_date": str(end_date), + } + self._initialize_device_params( + [ + "goals", + "daily_summary", + "exercises_cardio", + "exercises_strength", + "lunch", + "breakfast", + "dinner", + "snacks", + ], + params, + { + "seed": 0, + "synthetic_start_date": "2022-03-01", + "synthetic_end_date": "2022-06-17", + "use_cache": True, + }, + ) + + def _default_params(self): + return { + "start_date": self.init_params["synthetic_start_date"], + "end_date": self.init_params["synthetic_end_date"], + } + + def _get_real(self, data_type, params): + return fetch_real_data( + self, params["start_date"], params["end_date"], data_type + ) + + def _filter_synthetic(self, data, data_type, params): + # Here we just return the data we've already generated, + # but index into it based on the params. Specifically, we + # want to return the data between the start and end dates. + + def date_str_to_obj(x): + return datetime.strptime(x, "%Y-%m-%d") + + # get the indices by subtracting against the start of the synthetic data + synthetic_start = date_str_to_obj(self.init_params["synthetic_start_date"]) + + start_idx = (date_str_to_obj(params["start_date"]) - synthetic_start).days + end_idx = (date_str_to_obj(params["end_date"]) - synthetic_start).days + + return data[start_idx:end_idx] + + def _gen_synthetic(self): + # generate random data according to seed + seed_everything(self.init_params["seed"]) + + # and based on start and end dates + ( + self.goals, + self.daily_summary, + self.exercises_cardio, + self.exercises_strength, + self.breakfast, + self.lunch, + self.dinner, + self.snacks, + ) = create_syn_data( + self.init_params["synthetic_start_date"], + self.init_params["synthetic_end_date"], + ) + + def _authenticate(self, auth_creds): + + # Using cookies stored on local machine to login to myfitnesspal + if "cookies" in auth_creds: + try: + client = myfitnesspal.Client(auth_creds["cookies"]) + except myfitnesspal.exceptions.MyfitnesspalLoginError as e: + raise Exception( + "Could not authenticate with MyFitnessPal using the cookies provided, retry using a local machine" + ) + else: + try: + client = myfitnesspal.Client() + except myfitnesspal.exceptions.MyfitnesspalLoginError as e: + raise Exception( + "Could not authenticate with MyFitnessPal using the cookies provided by your device, retry using a local machine" + ) + + self.client = client diff --git a/wearipedia/devices/qualtrics/qualtrics_fetch.py b/wearipedia/devices/qualtrics/qualtrics_fetch.py new file mode 100644 index 00000000..fc3b7998 --- /dev/null +++ b/wearipedia/devices/qualtrics/qualtrics_fetch.py @@ -0,0 +1,173 @@ +import pandas as pd + +# Functions to generate data +# These functions are used to generate data that closely mimic the +# data that is generated by the MyFitnessPal API + + +def goal_generator(self, days): + # creating an empty list to store the goals + goals = [] + + # for each day in the date range, we get the goals for that day + for day in days: + # using the client, we get the goals for the day + res = self.client.get_date(int(day.year), int(day.month), int(day.day)).goals + # we add the date to the goals + res["date"] = day + # we append the goals to the list + goals.append(res) + # we return the list of goals + return goals + + +def daily_summary_generator(self, days): + # creating an empty list to store the daily summary + summary = [] + + # for each day in the date range, we get the daily summary for that day + for day in days: + # using the client, we get the daily summary for the day + res = self.client.get_date(int(day.year), int(day.month), int(day.day)).totals + # we add the date to the daily summary + res["date"] = day + # we append the daily summary to the list + summary.append(res) + + # we return the list of daily summary + return summary + + +def cardio_generator(self, days): + # creating an empty list to store the cardio exercises + cardio = [] + + # for each day in the date range, we get the cardio exercises for that day + for day in days: + # using the client, we get the cardio exercises for the day + res = ( + self.client.get_date(int(day.year), int(day.month), int(day.day)) + .exercises[0] + .get_as_list() + ) + # we add the date to the cardio exercises + res = [{"day": day}] + res + # we append the cardio exercises to the list + cardio.append(res) + # we return the list of cardio exercises + return cardio + + +def strength_generator(self, days): + # creating an empty list to store the strength exercises + strength = [] + + # for each day in the date range, we get the strength exercises for that day + for day in days: + # using the client, we get the strength exercises for the day + res = ( + self.client.get_date(int(day.year), int(day.month), int(day.day)) + .exercises[1] + .get_as_list() + ) + # we add the date to the strength exercises + res = [{"day": day}] + res + # we append the strength exercises to the list + strength.append(res) + # we return the list of strength exercises + return strength + + +def food_fetcher(client, type, days): + # creating an empty list to store the food items + food = [] + + # for each day in the date range, we get the food items for that day + for day in days: + # using the client, we get the food items for the day + res = ( + client.get_date(int(day.year), int(day.month), int(day.day)) + .meals[type] + .get_as_list() + ) + + # if the length of the list is 0, skip the totals + if len(res) == 0: + res.append({"date": day}) + + # if the length of the list is not 0, we add the date and the totals to the list + else: + res[0]["date"] = day + res[0]["totals"] = ( + client.get_date(int(day.year), int(day.month), int(day.day)) + .meals[type] + .totals + ) + + # we append the food items to the list + food.append(res) + # we return the list of food items + return food + + +def breakfast_generator(self, days): + # creating an empty list to store the breakfast foods + return food_fetcher(self.client, 0, days) + + +def lunch_generator(self, days): + # creating an empty list to store the lunch foods + return food_fetcher(self.client, 1, days) + + +def dinner_generator(self, days): + # creating an empty list to store the dinner foods + return food_fetcher(self.client, 2, days) + + +def snacks_generator(self, days): + # creating an empty list to store the snacks foods + return food_fetcher(self.client, 3, days) + + +# This is the function that will be used to fetch data from MyFitnessPal +def fetch_real_data(self, start_date, end_date, data_type): + + # if the client is not set, we need to login + if self.client == None: + raise Exception("Not Authenticated, login and try again") + + # creating the date range from the start and end dates + days = pd.date_range(start_date, end_date, freq="D") + + # if the data type is goals, we need to get the goals for each day using the client + if data_type == "goals": + return goal_generator(self, days) + + # if the data type is daily_summary, we need to get the daily summary for each day using the client + elif data_type == "daily_summary": + return daily_summary_generator(self, days) + + # if the data type is exercises_cardio, we need to get the cardio exercises for each day using the client + if data_type == "exercises_cardio": + return cardio_generator(self, days) + + # if the data type is exercises_strength, we need to get the strength exercises for each day using the client + if data_type == "exercises_strength": + return strength_generator(self, days) + + # if the data type is breakfast, we need to get the other exercises for each day using the client + if data_type == "breakfast": + return breakfast_generator(self, days) + + # if the data type is lunch, we need to get the lunch foods for each day using the client + if data_type == "lunch": + return lunch_generator(self, days) + + # if the data type is dinner, we need to get the dinner foods for each day using the client + if data_type == "dinner": + return dinner_generator(self, days) + + # if the data type is snacks, we need to get the snacks for each day using the client + if data_type == "snacks": + return snacks_generator(self, days) diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py new file mode 100644 index 00000000..01dd1fa2 --- /dev/null +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -0,0 +1,210 @@ +import json +import os + +import numpy as np +import pandas as pd + + +def create_syn_data(start_date, end_date): + + # Create a list of dates between start_date and end_date + dates = pd.date_range(start_date, end_date, freq="D") + + # Create empty lists to store data + goals = [] + daily_summary = [] + strength_exercises = [] + cardio_exercises = [] + breakfast = [] + lunch = [] + dinner = [] + snacks = [] + + # Functions to generate synthetic data + def syn_calories(x): + return np.round(np.random.normal(2500, 200), 1) + + def syn_carbs(x): + return np.round(np.random.normal(250, 75), 1) + + def syn_fat(x): + return np.round(np.random.normal(75, 25), 1) + + def syn_protein(x): + return np.round(max(0.0, np.random.normal(100, 33)), 1) + + def syn_sodium(x): + return np.round(np.random.normal(2300, 500), 1) + + def syn_sugar(x): + return np.round(np.random.normal(75, 25), 1) + + path = os.path.abspath( + os.path.join(os.path.dirname(__file__), "myfitnesspal_syn_data.json") + ) + + # Loading data from our json file + data = json.load(open(path, "r")) + + # wearipedia/devices/myfitnesspal/myfitnesspal_syn_data.json + + # Scraped a list of all exercises from MyFitnessPal and used GPT3 to seperate them into strength and cardio exercises + exercises_cardio = data["exercises_cardio"] + # List of exercises that are strength exercises scraped from myfitnesspal.com + exercises_strength = data["exercises_strength"] + + # List of top 15 breakfast foods from the USDA + breakfast_foods = data["breakfast_foods"] + + # List of top 15 lunch foods from the USDA + lunch_foods = data["lunch_foods"] + # List of top 15 dinner foods from the USDA + dinner_foods = data["dinner_foods"] + # List of top 15 snack foods from the USDA + snack_foods = data["snack_foods"] + + def create_food_item(item_name, food_item): + return [ + {"day": pd.Timestamp(day)}, + { + "name": item_name, + "nutrition_information": { + "calories": float(food_item["calories"]), + "carbohydrates": float(food_item["carbohydrates"]), + "fat": float(food_item["fat"]), + "protein": float(food_item["protein"]), + "sodium": float(food_item["sodium"]), + "sugar": float(food_item["sugar"]), + }, + "totals": { + "calories": float(food_item["calories"]), + "carbohydrates": float(food_item["carbohydrates"]), + "fat": float(food_item["fat"]), + "protein": float(food_item["protein"]), + "sodium": float(food_item["sodium"]), + "sugar": float(food_item["sugar"]), + }, + }, + ] + + for day in dates: + + # Creating a randomly generated summary of the day's food goal + goals.append( + { + "calories": syn_calories(day), + "carbohydrates": syn_carbs(day), + "fat": syn_fat(day), + "protein": syn_protein(day), + "sodium": syn_sodium(day), + "sugar": syn_sugar(day), + "date": pd.Timestamp(day), + } + ) + + # Creating a randomly generated summary of the day's food intake + daily_summary.append( + { + "calories": syn_calories(day), + "carbohydrates": syn_carbs(day), + "fat": syn_fat(day), + "protein": syn_protein(day), + "sodium": syn_sodium(day), + "sugar": syn_sugar(day), + "date": pd.Timestamp(day), + } + ) + + # Creating a randomly generated list of cardio exercises for the day + cardio = [{"day": pd.Timestamp(day)}] + + # We will randomly select between 1 and 3 cardio exercises + cardio_count = np.random.randint(1, 3) + + # We will randomly select between 1 and 3 cardio exercises + random_exercises = np.random.choice( + exercises_cardio, cardio_count, replace=False + ) + + # Adding each exercise to the list of cardio exercises for that day + for exercise in random_exercises: + minutes = np.random.randint(10, 60) + syn_exercise = { + "name": exercise, + "nutrition_information": { + "minutes": minutes, + "calories burned": minutes * max(2, np.random.uniform(3, 5)), + }, + } + + # Adding the exercise to the list of cardio exercises for that day + cardio.append(syn_exercise) + + # Adding the list of cardio exercises for that day to the list of all cardio exercises + cardio_exercises.append(cardio) + + # Creating a randomly generated list of strength exercises for the day + strength = [] + + # Adding the date to the list of strength exercises for that day + strength.append({"date": pd.Timestamp(day)}) + + # We will randomly select between 1 and 10 strength exercises + exercise_count = np.random.randint(1, 10) + + # We will randomly select between 1 and 10 strength exercises + random_exercises = np.random.choice( + exercises_strength, exercise_count, replace=False + ) + + # Adding each exercise to the list of strength exercises for that day + for exercise in random_exercises: + syn_exercise = { + "name": exercise, + "nutrition_information": { + "sets": float(np.random.randint(2, 5)), + "reps/set": float(np.round(np.random.uniform(10, 2))), + "weight/set": float(np.random.randint(5, 100)), + }, + } + strength.append(syn_exercise) + strength_exercises.append(strength) + + # Creating a randomly generated list of breakfast foods for the day + breakfast_item_name = np.random.choice(list(breakfast_foods.keys())) + breakfast_item = breakfast_foods[breakfast_item_name] + + # Adding the breakfast food to the list of breakfast foods for that day + breakfast.append(create_food_item(breakfast_item_name, breakfast_item)) + + # Creating a randomly generated list of lunch foods for the day + lunch_item_name = np.random.choice(list(lunch_foods.keys())) + lunch_item = lunch_foods[lunch_item_name] + + # Adding the lunch food to the list of lunch foods for that day + lunch.append(create_food_item(lunch_item_name, lunch_item)) + + # Creating a randomly generated list of dinner foods for the day + dinner_item_name = np.random.choice(list(dinner_foods.keys())) + dinner_item = dinner_foods[dinner_item_name] + + # Adding the dinner food to the list of dinner foods for that day + dinner.append(create_food_item(dinner_item_name, dinner_item)) + + # Creating a randomly generated list of snack foods for the day + snack_item_name = np.random.choice(list(snack_foods.keys())) + snack_item = snack_foods[snack_item_name] + + # Adding the snack food to the list of snack foods for that day + snacks.append(create_food_item(snack_item_name, snack_item)) + + return ( + goals, + daily_summary, + cardio_exercises, + strength_exercises, + breakfast, + lunch, + dinner, + snacks, + ) From 485e3b0fed6b6cbdfb3f113fbedbd3d27bf79e5a Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Mon, 13 May 2024 21:57:09 -0700 Subject: [PATCH 2/8] Qualtrics intergation --- poetry.lock | 46 +- pyproject.toml | 1 + wearipedia/__init__.py | 1 + wearipedia/devices/qualtrics/__init__.py | 2 +- wearipedia/devices/qualtrics/qualtrics.py | 134 +---- .../devices/qualtrics/qualtrics_fetch.py | 185 +------ wearipedia/devices/qualtrics/qualtrics_gen.py | 473 ++++++++++-------- 7 files changed, 356 insertions(+), 486 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3452989f..7c15ed66 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -3336,8 +3336,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.1" pytz = ">=2020.1" @@ -4151,6 +4151,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -4158,8 +4159,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -4176,6 +4185,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -4183,6 +4193,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -4335,6 +4346,23 @@ packaging = "*" [package.extras] test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] +[[package]] +name = "qualtricsapi" +version = "0.6.1" +description = "QualtricsAPI is a lightweight Python library for the Qualtrics Web API." +optional = false +python-versions = "*" +files = [ + {file = "QualtricsAPI-0.6.1-py3-none-any.whl", hash = "sha256:f58564d668dba899fb5e29de7de9480c520ca1c9bc4c217309006a646cc19241"}, + {file = "QualtricsAPI-0.6.1.tar.gz", hash = "sha256:f12647809116ed35ff17d1a401840f9caa082250ace843fdcbffa8007d70c667"}, +] + +[package.dependencies] +numpy = "*" +pandas = "*" +python-dateutil = "*" +requests = "*" + [[package]] name = "referencing" version = "0.32.1" @@ -4679,24 +4707,24 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, @@ -4704,7 +4732,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, @@ -4712,7 +4740,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, @@ -4720,7 +4748,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, @@ -5935,4 +5963,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9, <4.0" -content-hash = "5be4fff5977cf2b72f95e679151ff9773ef26050057de89432ca666de9af0486" +content-hash = "59900c50ad1732a6c05b689c9a8f2e49b59edec569db53e542963b8346c52984" diff --git a/pyproject.toml b/pyproject.toml index 68af362b..b59792d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ fastapi = "0.101" typing-extensions = "4.5.0" kaleido = "0.2.1" lida = "^0.0.11" +qualtricsapi = "^0.6.1" [tool.poetry.group.dev.dependencies] bandit = "^1.7.1" diff --git a/wearipedia/__init__.py b/wearipedia/__init__.py index d0dcab3f..41eea590 100644 --- a/wearipedia/__init__.py +++ b/wearipedia/__init__.py @@ -81,6 +81,7 @@ def get_all_device_names(): "polar/vantage", "strava/strava", "myfitnesspal/myfitnesspal", + "qualtrics/qualtrics", ] diff --git a/wearipedia/devices/qualtrics/__init__.py b/wearipedia/devices/qualtrics/__init__.py index 32920181..0b4f2cfb 100644 --- a/wearipedia/devices/qualtrics/__init__.py +++ b/wearipedia/devices/qualtrics/__init__.py @@ -1 +1 @@ -from .myfitnesspal import * +from .qualtrics import * diff --git a/wearipedia/devices/qualtrics/qualtrics.py b/wearipedia/devices/qualtrics/qualtrics.py index b01b1b15..b2ea52b6 100644 --- a/wearipedia/devices/qualtrics/qualtrics.py +++ b/wearipedia/devices/qualtrics/qualtrics.py @@ -1,136 +1,56 @@ -import os -import pickle -from datetime import datetime - -import myfitnesspal - -from ...devices.device import BaseDevice from ...utils import seed_everything -from .myfitnesspal_fetch import * -from .myfitnesspal_synthetic import * - -class_name = "MyFitnessPal" - - -class MyFitnessPal(BaseDevice): - - """This device allows you to work with data from the `MyFitnessPal `_ app. - Available datatypes for this device are: - - * `goals`: a list that contains goals data for each day - - * `daily_summary`: a list that contains daily summary data for each day - - * `exercises_cardio`: a list that contains cardio exercises data for each day - - * `exercises_strength`: a list that contains strength exercises data for each day - - * `lunch`: a list that contains lunch data for each day +from ..device import BaseDevice +from .qualtrics_fetch import * +from .qualtrics_gen import * - * `breakfast`: a list that contains breakfast data for each day +class_name = "Qualtrics" - * `dinner`: a list that contains dinner data for each day - - * `snacks`: a list that contains snacks data for each day - - :param seed: random seed for synthetic data generation, defaults to 0 - :type seed: int, optional - :param synthetic_start_date: start date for synthetic data generation, defaults to "2022-03-01" - :type synthetic_start_date: str, optional - :param synthetic_end_date: end date for synthetic data generation, defaults to "2022-06-17" - :type synthetic_end_date: str, optional - :param use_cache: decide whether to cache the credentials, defaults to True - :type use_cache: bool, optional - """ - - def __init__(self, seed=0, start_date="2022-03-01", end_date="2022-06-17"): +class Qualtrics(BaseDevice): + def __init__(self, seed=0, token="M4EoyhFeA6s6YpT5BB3ETS4K1fniU1f4Y7TZddkl", data_center="stanforduniversity", survey="SV_6FDXhJO8mupUnuC"): params = { "seed": seed, - "start_date": str(start_date), - "end_date": str(end_date), + "token": str(token), + "data_center": str(data_center), + "survey": str(survey), } + self._initialize_device_params( [ - "goals", - "daily_summary", - "exercises_cardio", - "exercises_strength", - "lunch", - "breakfast", - "dinner", - "snacks", + "responses", ], params, { "seed": 0, - "synthetic_start_date": "2022-03-01", - "synthetic_end_date": "2022-06-17", - "use_cache": True, + "token": "M4EoyhFeA6s6YpT5BB3ETS4K1fniU1f4Y7TZddkl", + "data_center": "stanforduniversity", + "survey": "SV_6FDXhJO8mupUnuC", }, ) def _default_params(self): return { - "start_date": self.init_params["synthetic_start_date"], - "end_date": self.init_params["synthetic_end_date"], + "token": self.init_params["synthetic_token"], + "data_center": self.init_params["synthetic_data_center"], + "survey": self.init_params["synthetic_survey"], } - def _get_real(self, data_type, params): + def _get_real(self, params): return fetch_real_data( - self, params["start_date"], params["end_date"], data_type + params["token"], params["data_center"], params["survey"] ) - def _filter_synthetic(self, data, data_type, params): - # Here we just return the data we've already generated, - # but index into it based on the params. Specifically, we - # want to return the data between the start and end dates. - - def date_str_to_obj(x): - return datetime.strptime(x, "%Y-%m-%d") - - # get the indices by subtracting against the start of the synthetic data - synthetic_start = date_str_to_obj(self.init_params["synthetic_start_date"]) - - start_idx = (date_str_to_obj(params["start_date"]) - synthetic_start).days - end_idx = (date_str_to_obj(params["end_date"]) - synthetic_start).days - - return data[start_idx:end_idx] + def _filter_synthetic(self, data, params): + return data def _gen_synthetic(self): - # generate random data according to seed seed_everything(self.init_params["seed"]) - - # and based on start and end dates ( - self.goals, - self.daily_summary, - self.exercises_cardio, - self.exercises_strength, - self.breakfast, - self.lunch, - self.dinner, - self.snacks, + self.responses, ) = create_syn_data( - self.init_params["synthetic_start_date"], - self.init_params["synthetic_end_date"], + self.init_params["synthetic_token"], + self.init_params["synthetic_data_center"], + self.init_params["synthetic_survey"], ) - def _authenticate(self, auth_creds): - - # Using cookies stored on local machine to login to myfitnesspal - if "cookies" in auth_creds: - try: - client = myfitnesspal.Client(auth_creds["cookies"]) - except myfitnesspal.exceptions.MyfitnesspalLoginError as e: - raise Exception( - "Could not authenticate with MyFitnessPal using the cookies provided, retry using a local machine" - ) - else: - try: - client = myfitnesspal.Client() - except myfitnesspal.exceptions.MyfitnesspalLoginError as e: - raise Exception( - "Could not authenticate with MyFitnessPal using the cookies provided by your device, retry using a local machine" - ) - - self.client = client + def _authenticate(self): + pass diff --git a/wearipedia/devices/qualtrics/qualtrics_fetch.py b/wearipedia/devices/qualtrics/qualtrics_fetch.py index fc3b7998..3be3e1b6 100644 --- a/wearipedia/devices/qualtrics/qualtrics_fetch.py +++ b/wearipedia/devices/qualtrics/qualtrics_fetch.py @@ -1,173 +1,12 @@ -import pandas as pd - -# Functions to generate data -# These functions are used to generate data that closely mimic the -# data that is generated by the MyFitnessPal API - - -def goal_generator(self, days): - # creating an empty list to store the goals - goals = [] - - # for each day in the date range, we get the goals for that day - for day in days: - # using the client, we get the goals for the day - res = self.client.get_date(int(day.year), int(day.month), int(day.day)).goals - # we add the date to the goals - res["date"] = day - # we append the goals to the list - goals.append(res) - # we return the list of goals - return goals - - -def daily_summary_generator(self, days): - # creating an empty list to store the daily summary - summary = [] - - # for each day in the date range, we get the daily summary for that day - for day in days: - # using the client, we get the daily summary for the day - res = self.client.get_date(int(day.year), int(day.month), int(day.day)).totals - # we add the date to the daily summary - res["date"] = day - # we append the daily summary to the list - summary.append(res) - - # we return the list of daily summary - return summary - - -def cardio_generator(self, days): - # creating an empty list to store the cardio exercises - cardio = [] - - # for each day in the date range, we get the cardio exercises for that day - for day in days: - # using the client, we get the cardio exercises for the day - res = ( - self.client.get_date(int(day.year), int(day.month), int(day.day)) - .exercises[0] - .get_as_list() - ) - # we add the date to the cardio exercises - res = [{"day": day}] + res - # we append the cardio exercises to the list - cardio.append(res) - # we return the list of cardio exercises - return cardio - - -def strength_generator(self, days): - # creating an empty list to store the strength exercises - strength = [] - - # for each day in the date range, we get the strength exercises for that day - for day in days: - # using the client, we get the strength exercises for the day - res = ( - self.client.get_date(int(day.year), int(day.month), int(day.day)) - .exercises[1] - .get_as_list() - ) - # we add the date to the strength exercises - res = [{"day": day}] + res - # we append the strength exercises to the list - strength.append(res) - # we return the list of strength exercises - return strength - - -def food_fetcher(client, type, days): - # creating an empty list to store the food items - food = [] - - # for each day in the date range, we get the food items for that day - for day in days: - # using the client, we get the food items for the day - res = ( - client.get_date(int(day.year), int(day.month), int(day.day)) - .meals[type] - .get_as_list() - ) - - # if the length of the list is 0, skip the totals - if len(res) == 0: - res.append({"date": day}) - - # if the length of the list is not 0, we add the date and the totals to the list - else: - res[0]["date"] = day - res[0]["totals"] = ( - client.get_date(int(day.year), int(day.month), int(day.day)) - .meals[type] - .totals - ) - - # we append the food items to the list - food.append(res) - # we return the list of food items - return food - - -def breakfast_generator(self, days): - # creating an empty list to store the breakfast foods - return food_fetcher(self.client, 0, days) - - -def lunch_generator(self, days): - # creating an empty list to store the lunch foods - return food_fetcher(self.client, 1, days) - - -def dinner_generator(self, days): - # creating an empty list to store the dinner foods - return food_fetcher(self.client, 2, days) - - -def snacks_generator(self, days): - # creating an empty list to store the snacks foods - return food_fetcher(self.client, 3, days) - - -# This is the function that will be used to fetch data from MyFitnessPal -def fetch_real_data(self, start_date, end_date, data_type): - - # if the client is not set, we need to login - if self.client == None: - raise Exception("Not Authenticated, login and try again") - - # creating the date range from the start and end dates - days = pd.date_range(start_date, end_date, freq="D") - - # if the data type is goals, we need to get the goals for each day using the client - if data_type == "goals": - return goal_generator(self, days) - - # if the data type is daily_summary, we need to get the daily summary for each day using the client - elif data_type == "daily_summary": - return daily_summary_generator(self, days) - - # if the data type is exercises_cardio, we need to get the cardio exercises for each day using the client - if data_type == "exercises_cardio": - return cardio_generator(self, days) - - # if the data type is exercises_strength, we need to get the strength exercises for each day using the client - if data_type == "exercises_strength": - return strength_generator(self, days) - - # if the data type is breakfast, we need to get the other exercises for each day using the client - if data_type == "breakfast": - return breakfast_generator(self, days) - - # if the data type is lunch, we need to get the lunch foods for each day using the client - if data_type == "lunch": - return lunch_generator(self, days) - - # if the data type is dinner, we need to get the dinner foods for each day using the client - if data_type == "dinner": - return dinner_generator(self, days) - - # if the data type is snacks, we need to get the snacks for each day using the client - if data_type == "snacks": - return snacks_generator(self, days) +import QualtricsAPI + +from QualtricsAPI.Setup import Credentials +from QualtricsAPI.Survey import Responses + +def fetch_real_data(token, data_center, survey): + """ + Fetches real data from the Qualtrics API. + """ + Credentials().qualtrics_api_credentials(token=token,data_center=data_center) + table = Responses().get_survey_responses(survey=survey, useLabels=True) + return table diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 01dd1fa2..6dd359c3 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -1,210 +1,291 @@ -import json -import os +import math +import random +from datetime import datetime, timedelta import numpy as np -import pandas as pd def create_syn_data(start_date, end_date): - - # Create a list of dates between start_date and end_date - dates = pd.date_range(start_date, end_date, freq="D") - - # Create empty lists to store data - goals = [] - daily_summary = [] - strength_exercises = [] - cardio_exercises = [] - breakfast = [] - lunch = [] - dinner = [] - snacks = [] - - # Functions to generate synthetic data - def syn_calories(x): - return np.round(np.random.normal(2500, 200), 1) - - def syn_carbs(x): - return np.round(np.random.normal(250, 75), 1) - - def syn_fat(x): - return np.round(np.random.normal(75, 25), 1) - - def syn_protein(x): - return np.round(max(0.0, np.random.normal(100, 33)), 1) - - def syn_sodium(x): - return np.round(np.random.normal(2300, 500), 1) - - def syn_sugar(x): - return np.round(np.random.normal(75, 25), 1) - - path = os.path.abspath( - os.path.join(os.path.dirname(__file__), "myfitnesspal_syn_data.json") - ) - - # Loading data from our json file - data = json.load(open(path, "r")) - - # wearipedia/devices/myfitnesspal/myfitnesspal_syn_data.json - - # Scraped a list of all exercises from MyFitnessPal and used GPT3 to seperate them into strength and cardio exercises - exercises_cardio = data["exercises_cardio"] - # List of exercises that are strength exercises scraped from myfitnesspal.com - exercises_strength = data["exercises_strength"] - - # List of top 15 breakfast foods from the USDA - breakfast_foods = data["breakfast_foods"] - - # List of top 15 lunch foods from the USDA - lunch_foods = data["lunch_foods"] - # List of top 15 dinner foods from the USDA - dinner_foods = data["dinner_foods"] - # List of top 15 snack foods from the USDA - snack_foods = data["snack_foods"] - - def create_food_item(item_name, food_item): - return [ - {"day": pd.Timestamp(day)}, - { - "name": item_name, - "nutrition_information": { - "calories": float(food_item["calories"]), - "carbohydrates": float(food_item["carbohydrates"]), - "fat": float(food_item["fat"]), - "protein": float(food_item["protein"]), - "sodium": float(food_item["sodium"]), - "sugar": float(food_item["sugar"]), - }, - "totals": { - "calories": float(food_item["calories"]), - "carbohydrates": float(food_item["carbohydrates"]), - "fat": float(food_item["fat"]), - "protein": float(food_item["protein"]), - "sodium": float(food_item["sodium"]), - "sugar": float(food_item["sugar"]), - }, - }, - ] - - for day in dates: - - # Creating a randomly generated summary of the day's food goal - goals.append( - { - "calories": syn_calories(day), - "carbohydrates": syn_carbs(day), - "fat": syn_fat(day), - "protein": syn_protein(day), - "sodium": syn_sodium(day), - "sugar": syn_sugar(day), - "date": pd.Timestamp(day), - } - ) - - # Creating a randomly generated summary of the day's food intake - daily_summary.append( - { - "calories": syn_calories(day), - "carbohydrates": syn_carbs(day), - "fat": syn_fat(day), - "protein": syn_protein(day), - "sodium": syn_sodium(day), - "sugar": syn_sugar(day), - "date": pd.Timestamp(day), - } - ) - - # Creating a randomly generated list of cardio exercises for the day - cardio = [{"day": pd.Timestamp(day)}] - - # We will randomly select between 1 and 3 cardio exercises - cardio_count = np.random.randint(1, 3) - - # We will randomly select between 1 and 3 cardio exercises - random_exercises = np.random.choice( - exercises_cardio, cardio_count, replace=False + """ + Generates synthetic data collected by Biostrap between a given start and end date. + + :param start_date: Start date (inclusive) as a string in the format "YYYY-MM-DD" + :type start_date: str + :param end_date: End date (inclusive) as a string in the format "YYYY-MM-DD" + :type end_date: str + + :return: A tuple consisting of: + - activities: Dictionary containing details of a random synthetic activity + - bpm: Dictionary representing beats per minute for every 10 seconds throughout the range + - brpm: Dictionary representing breaths per minute based on bpm values for every minute throughout the range + - hrv: Dictionary representing heart rate variability for every 10 seconds throughout the range + - spo2: Dictionary representing blood oxygen saturation for every 10 seconds + - rest_cals: Dictionary representing resting calories burned each day + - work_cals: Dictionary representing workout calories burned each day + - active_cals: Dictionary representing active calories burned each day + - step_cals: Dictionary representing calories burned from steps each day + - total_cals: Dictionary representing total calories burned each day + - sleep_session: Dictionary representing moments of movement during typical sleeping hours + - sleep_detail: Dictionary representing details of a synthetic sleep session + - steps: Dictionary representing steps taken every minute throughout the range + - distance: Dictionary representing distance covered (based on steps) every minute throughout the range + + :rtype: Tuple[Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict] + """ + + # Convert the provided strings to datetime objects + start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") + end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") + + TZ_OFFSET = -420 + + # Adjusted Gaussian noise functions for each biometric with more realistic standard deviations + def gaussian_noise_bpm(): + return np.random.normal(0, 2) # Reduced standard deviation for less noise + + def gaussian_noise_brpm(): + return np.random.normal(0, 0.5) # Reduced standard deviation for less noise + + def gaussian_noise_hrv(): + return np.random.normal(0, 1) # Reduced standard deviation for less noise + + def gaussian_noise_spo2(): + return np.random.normal(0, 0.05) # Reduced standard deviation for minimal noise + + def synthetic_biometrics(start_date_obj, end_date_obj): + bpm = {} + brpm = {} + hrv = {} + spo2 = {} + + curr_date_obj = start_date_obj + time_index = 0 + + # Initial values for random walk + bpm_current = 45 + brpm_current = 16 + hrv_current = 40 + spo2_current = 98 + + # Target means and bounds + target_mean_bpm = 70 + target_mean_brpm = 16 + target_mean_hrv = 50 + target_mean_spo2 = 98 + + bpm_lower_bound, bpm_upper_bound = 40, 120 + brpm_lower_bound, brpm_upper_bound = 12, 20 + hrv_lower_bound, hrv_upper_bound = 20, 100 + spo2_lower_bound, spo2_upper_bound = 95, 100 + + # Store the last minute's average bpm to calculate brpm + last_minute_bpm = [] + + while curr_date_obj <= end_date_obj: + curr_time = curr_date_obj + while curr_time < curr_date_obj + timedelta(days=1): + key = (curr_time.strftime("%Y-%m-%d %H:%M:%S"), TZ_OFFSET) + + # Update bpm, hrv, and spo2 every 10 seconds + bpm_mean_reversion = ( + target_mean_bpm - bpm_current + ) * 0.01 # Changed from 0.1 + bpm_current += bpm_mean_reversion + gaussian_noise_bpm() + bpm_current = max(bpm_lower_bound, min(bpm_upper_bound, bpm_current)) + bpm[key] = int(bpm_current) + + hrv_mean_reversion = (target_mean_hrv - hrv_current) * 0.1 + hrv_current += hrv_mean_reversion + gaussian_noise_hrv() + hrv_current = max(hrv_lower_bound, min(hrv_upper_bound, hrv_current)) + hrv[key] = int(hrv_current) + + spo2_mean_reversion = (target_mean_spo2 - spo2_current) * 0.1 + spo2_current += spo2_mean_reversion + gaussian_noise_spo2() + spo2_current = max( + spo2_lower_bound, min(spo2_upper_bound, spo2_current) + ) + spo2[key] = int(spo2_current) + + # Update brpm every minute + if time_index % 60 == 0: + brpm_mean_reversion = (target_mean_brpm - brpm_current) * 0.1 + brpm_current += brpm_mean_reversion + gaussian_noise_brpm() + brpm_current = max( + brpm_lower_bound, min(brpm_upper_bound, brpm_current) + ) + brpm[key] = int(brpm_current) + + # Increment time and index + curr_time += timedelta(seconds=10) + time_index += 10 + + curr_date_obj += timedelta(days=1) + + return bpm, brpm, hrv, spo2 + + def synthetic_steps_distance_per_minute(bpm_dict): + steps_dict = {} + distance_dict = {} + + # For each minute, we'll check the BPM to determine steps + for key, bpm_value in bpm_dict.items(): + dt, _ = key + + # Extracting hour to check for sleeping hours + curr_hour = int(dt.split(" ")[1].split(":")[0]) + + if dt.endswith("00"): # Checking if it's on a per-minute mark + if 23 <= curr_hour or curr_hour < 6: # typical sleeping hours + steps = random.choice( + [0, 0, 0, 0, 1, 2] + ) # Mostly zero, but sometimes a small number indicating tossing/turning in sleep + elif bpm_value < 60: + steps = random.randint(0, 20) # Relatively calm/resting + elif bpm_value < 80: + steps = random.randint(20, 40) # Maybe just light walking + else: + steps = random.randint(40, 120) # Active movement or jogging + + distance = steps * random.uniform(0.7, 0.8) + date_str = dt.split()[0] + time_str = dt.split()[1] + + steps_dict[f"{date_str} {time_str}"] = steps + distance_dict[f"{date_str} {time_str}"] = distance + + return steps_dict, distance_dict + + def synthetic_daily_calories(bpm_dict, steps_dict): + rest_cals_dict = {} + work_cals_dict = {} + active_cals_dict = {} + step_cals_dict = {} + total_cals_dict = {} + + daily_bpm_averages = {} + + # Precompute average BPM per day + for (dt, _), v in bpm_dict.items(): + date_str = dt.split(" ")[0] + daily_bpm_averages.setdefault(date_str, []).append(v) + + # Compute average BPM for each day + for date_str, bpm_vals in daily_bpm_averages.items(): + avg_bpm = sum(bpm_vals) / len(bpm_vals) + daily_bpm_averages[date_str] = avg_bpm + + # Calculate calories based on the precomputed average BPMs + for date_str, avg_bpm in daily_bpm_averages.items(): + if avg_bpm < 60: + active_cals = random.randint(50, 100) # relatively inactive + elif avg_bpm < 80: + active_cals = random.randint(100, 200) # moderately active + else: + active_cals = random.randint(200, 300) # very active + + steps_val = sum( + [val for dt, val in steps_dict.items() if dt.startswith(date_str)] + ) + + rest_cals_dict[date_str] = random.randint(1000, 1300) + work_cals_dict[date_str] = random.randint(300, 600) + step_cals_dict[date_str] = steps_val * 0.05 + active_cals_dict[date_str] = active_cals + total_cals_dict[date_str] = ( + rest_cals_dict[date_str] + + work_cals_dict[date_str] + + step_cals_dict[date_str] + + active_cals_dict[date_str] + ) + + return ( + rest_cals_dict, + work_cals_dict, + active_cals_dict, + step_cals_dict, + total_cals_dict, ) - # Adding each exercise to the list of cardio exercises for that day - for exercise in random_exercises: - minutes = np.random.randint(10, 60) - syn_exercise = { - "name": exercise, - "nutrition_information": { - "minutes": minutes, - "calories burned": minutes * max(2, np.random.uniform(3, 5)), - }, - } - - # Adding the exercise to the list of cardio exercises for that day - cardio.append(syn_exercise) - - # Adding the list of cardio exercises for that day to the list of all cardio exercises - cardio_exercises.append(cardio) - - # Creating a randomly generated list of strength exercises for the day - strength = [] - - # Adding the date to the list of strength exercises for that day - strength.append({"date": pd.Timestamp(day)}) - - # We will randomly select between 1 and 10 strength exercises - exercise_count = np.random.randint(1, 10) - - # We will randomly select between 1 and 10 strength exercises - random_exercises = np.random.choice( - exercises_strength, exercise_count, replace=False + def synthetic_activity(): + # A sample workout + activity_date = (start_date_obj + (end_date_obj - start_date_obj) / 2).strftime( + "%Y-%m-%d" + ) # choose a day in the middle of the range + + return { + "activity_date": activity_date, + "type": "Running", + "duration": timedelta(minutes=random.randint(20, 60)), + "distance": random.uniform(3, 10), + "calories_burned": random.randint(200, 500), + "avg_bpm": random.randint(80, 150), + "peak_bpm": random.randint(150, 180), + "steps_taken": random.randint(3000, 10000), + "intensity": random.choice(["light", "moderate", "high"]), + } + + def synthetic_sleep_session(bpm_dict): + # Extracting nighttime bpm readings + night_movements = { + k: v + for k, v in bpm_dict.items() + if 23 <= int(k[0].split(" ")[1].split(":")[0]) + or int(k[0].split(" ")[1].split(":")[0]) < 6 + } + # Filter out readings where bpm indicates deep sleep (low bpm) + night_movements = {k: v for k, v in night_movements.items() if v > 65} + return night_movements + + def synthetic_sleep_detail(): + sleep_date = (start_date_obj + (end_date_obj - start_date_obj) / 2).strftime( + "%Y-%m-%d" ) + total_sleep_duration = 8 # Assuming 8 hours sleep + + light_sleep = random.uniform(0.4, 0.6) * total_sleep_duration + deep_sleep = random.uniform(0.2, 0.3) * total_sleep_duration + rem_sleep = total_sleep_duration - (light_sleep + deep_sleep) + + return { + "sleep_date": sleep_date, + "light_sleep": light_sleep, + "deep_sleep": deep_sleep, + "rem_sleep": rem_sleep, + "awake_time": random.uniform(0.1, 0.3), + "times_awoken": random.randint(1, 5), + } + + # Generate biometric, steps, and distance data + bpm, brpm, hrv, spo2 = synthetic_biometrics(start_date_obj, end_date_obj) + steps, distance = synthetic_steps_distance_per_minute(bpm) + + # Generate daily calories based on steps and bpm + rest_cals, work_cals, active_cals, step_cals, total_cals = synthetic_daily_calories( + bpm, steps + ) - # Adding each exercise to the list of strength exercises for that day - for exercise in random_exercises: - syn_exercise = { - "name": exercise, - "nutrition_information": { - "sets": float(np.random.randint(2, 5)), - "reps/set": float(np.round(np.random.uniform(10, 2))), - "weight/set": float(np.random.randint(5, 100)), - }, - } - strength.append(syn_exercise) - strength_exercises.append(strength) - - # Creating a randomly generated list of breakfast foods for the day - breakfast_item_name = np.random.choice(list(breakfast_foods.keys())) - breakfast_item = breakfast_foods[breakfast_item_name] - - # Adding the breakfast food to the list of breakfast foods for that day - breakfast.append(create_food_item(breakfast_item_name, breakfast_item)) - - # Creating a randomly generated list of lunch foods for the day - lunch_item_name = np.random.choice(list(lunch_foods.keys())) - lunch_item = lunch_foods[lunch_item_name] - - # Adding the lunch food to the list of lunch foods for that day - lunch.append(create_food_item(lunch_item_name, lunch_item)) - - # Creating a randomly generated list of dinner foods for the day - dinner_item_name = np.random.choice(list(dinner_foods.keys())) - dinner_item = dinner_foods[dinner_item_name] - - # Adding the dinner food to the list of dinner foods for that day - dinner.append(create_food_item(dinner_item_name, dinner_item)) + # Generate activity data + activities = synthetic_activity() - # Creating a randomly generated list of snack foods for the day - snack_item_name = np.random.choice(list(snack_foods.keys())) - snack_item = snack_foods[snack_item_name] + # Generate sleep session data + sleep_session = synthetic_sleep_session(bpm) - # Adding the snack food to the list of snack foods for that day - snacks.append(create_food_item(snack_item_name, snack_item)) + # Generate sleep detail data + sleep_detail = synthetic_sleep_detail() return ( - goals, - daily_summary, - cardio_exercises, - strength_exercises, - breakfast, - lunch, - dinner, - snacks, + activities, + bpm, + brpm, + hrv, + spo2, + rest_cals, + work_cals, + active_cals, + step_cals, + total_cals, + sleep_session, + sleep_detail, + steps, + distance, ) From e558d1de48b82f2350b17e136cda3eb2149404ed Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Tue, 14 May 2024 15:32:04 -0700 Subject: [PATCH 3/8] Qualtrics integration complete --- tests/devices/qualtrics/test_qualtrics.py | 21 ++ wearipedia/devices/qualtrics/qualtrics.py | 30 +- .../devices/qualtrics/qualtrics_fetch.py | 14 +- wearipedia/devices/qualtrics/qualtrics_gen.py | 299 +----------------- .../devices/qualtrics/sample_survey.csv | 24 ++ 5 files changed, 69 insertions(+), 319 deletions(-) create mode 100644 tests/devices/qualtrics/test_qualtrics.py create mode 100644 wearipedia/devices/qualtrics/sample_survey.csv diff --git a/tests/devices/qualtrics/test_qualtrics.py b/tests/devices/qualtrics/test_qualtrics.py new file mode 100644 index 00000000..50e07f83 --- /dev/null +++ b/tests/devices/qualtrics/test_qualtrics.py @@ -0,0 +1,21 @@ +import pytest +import pandas as pd +import wearipedia + +@pytest.mark.parametrize("real", [True, False]) +def test_qualtrics(real): + + synthetic_survey = "your_survey_id_here" + + device = wearipedia.get_device( + "qualtrics/qualtrics", + survey=synthetic_survey, + ) + + if real: + wearipedia._authenticate_device("qualtrics", device) + + data = device.get_data('responses') + + # Check that the data is a pandas DataFrame + assert isinstance(data, pd.DataFrame), f"Data is not a pandas DataFrame" diff --git a/wearipedia/devices/qualtrics/qualtrics.py b/wearipedia/devices/qualtrics/qualtrics.py index b2ea52b6..9e4a03ed 100644 --- a/wearipedia/devices/qualtrics/qualtrics.py +++ b/wearipedia/devices/qualtrics/qualtrics.py @@ -3,14 +3,14 @@ from .qualtrics_fetch import * from .qualtrics_gen import * +from QualtricsAPI.Setup import Credentials + class_name = "Qualtrics" class Qualtrics(BaseDevice): - def __init__(self, seed=0, token="M4EoyhFeA6s6YpT5BB3ETS4K1fniU1f4Y7TZddkl", data_center="stanforduniversity", survey="SV_6FDXhJO8mupUnuC"): + def __init__(self, seed=0, survey="SV_6FDXhJO8mupUnuC"): params = { "seed": seed, - "token": str(token), - "data_center": str(data_center), "survey": str(survey), } @@ -21,36 +21,30 @@ def __init__(self, seed=0, token="M4EoyhFeA6s6YpT5BB3ETS4K1fniU1f4Y7TZddkl", dat params, { "seed": 0, - "token": "M4EoyhFeA6s6YpT5BB3ETS4K1fniU1f4Y7TZddkl", - "data_center": "stanforduniversity", - "survey": "SV_6FDXhJO8mupUnuC", + "synthetic_survey": "your_survey_id_here", }, ) def _default_params(self): return { - "token": self.init_params["synthetic_token"], - "data_center": self.init_params["synthetic_data_center"], "survey": self.init_params["synthetic_survey"], } - def _get_real(self, params): + def _get_real(self, data_type, params): return fetch_real_data( - params["token"], params["data_center"], params["survey"] + params["survey"] ) - def _filter_synthetic(self, data, params): + def _filter_synthetic(self, data, data_type, params): return data def _gen_synthetic(self): seed_everything(self.init_params["seed"]) - ( - self.responses, - ) = create_syn_data( - self.init_params["synthetic_token"], - self.init_params["synthetic_data_center"], + self.responses = create_syn_data( self.init_params["synthetic_survey"], ) - def _authenticate(self): - pass + def _authenticate(self, auth_creds): + token = auth_creds["token"] + data_center = auth_creds["data_center"] + Credentials().qualtrics_api_credentials(token=token, data_center=data_center) diff --git a/wearipedia/devices/qualtrics/qualtrics_fetch.py b/wearipedia/devices/qualtrics/qualtrics_fetch.py index 3be3e1b6..8a60f05c 100644 --- a/wearipedia/devices/qualtrics/qualtrics_fetch.py +++ b/wearipedia/devices/qualtrics/qualtrics_fetch.py @@ -1,12 +1,6 @@ -import QualtricsAPI - -from QualtricsAPI.Setup import Credentials from QualtricsAPI.Survey import Responses +from QualtricsAPI.Setup import Credentials -def fetch_real_data(token, data_center, survey): - """ - Fetches real data from the Qualtrics API. - """ - Credentials().qualtrics_api_credentials(token=token,data_center=data_center) - table = Responses().get_survey_responses(survey=survey, useLabels=True) - return table +def fetch_real_data(survey): + responses = Responses().get_survey_responses(survey=survey, useLabels=True) + return responses diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 6dd359c3..31feaa3e 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -1,291 +1,8 @@ -import math -import random -from datetime import datetime, timedelta - -import numpy as np - - -def create_syn_data(start_date, end_date): - """ - Generates synthetic data collected by Biostrap between a given start and end date. - - :param start_date: Start date (inclusive) as a string in the format "YYYY-MM-DD" - :type start_date: str - :param end_date: End date (inclusive) as a string in the format "YYYY-MM-DD" - :type end_date: str - - :return: A tuple consisting of: - - activities: Dictionary containing details of a random synthetic activity - - bpm: Dictionary representing beats per minute for every 10 seconds throughout the range - - brpm: Dictionary representing breaths per minute based on bpm values for every minute throughout the range - - hrv: Dictionary representing heart rate variability for every 10 seconds throughout the range - - spo2: Dictionary representing blood oxygen saturation for every 10 seconds - - rest_cals: Dictionary representing resting calories burned each day - - work_cals: Dictionary representing workout calories burned each day - - active_cals: Dictionary representing active calories burned each day - - step_cals: Dictionary representing calories burned from steps each day - - total_cals: Dictionary representing total calories burned each day - - sleep_session: Dictionary representing moments of movement during typical sleeping hours - - sleep_detail: Dictionary representing details of a synthetic sleep session - - steps: Dictionary representing steps taken every minute throughout the range - - distance: Dictionary representing distance covered (based on steps) every minute throughout the range - - :rtype: Tuple[Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict, Dict] - """ - - # Convert the provided strings to datetime objects - start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") - end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") - - TZ_OFFSET = -420 - - # Adjusted Gaussian noise functions for each biometric with more realistic standard deviations - def gaussian_noise_bpm(): - return np.random.normal(0, 2) # Reduced standard deviation for less noise - - def gaussian_noise_brpm(): - return np.random.normal(0, 0.5) # Reduced standard deviation for less noise - - def gaussian_noise_hrv(): - return np.random.normal(0, 1) # Reduced standard deviation for less noise - - def gaussian_noise_spo2(): - return np.random.normal(0, 0.05) # Reduced standard deviation for minimal noise - - def synthetic_biometrics(start_date_obj, end_date_obj): - bpm = {} - brpm = {} - hrv = {} - spo2 = {} - - curr_date_obj = start_date_obj - time_index = 0 - - # Initial values for random walk - bpm_current = 45 - brpm_current = 16 - hrv_current = 40 - spo2_current = 98 - - # Target means and bounds - target_mean_bpm = 70 - target_mean_brpm = 16 - target_mean_hrv = 50 - target_mean_spo2 = 98 - - bpm_lower_bound, bpm_upper_bound = 40, 120 - brpm_lower_bound, brpm_upper_bound = 12, 20 - hrv_lower_bound, hrv_upper_bound = 20, 100 - spo2_lower_bound, spo2_upper_bound = 95, 100 - - # Store the last minute's average bpm to calculate brpm - last_minute_bpm = [] - - while curr_date_obj <= end_date_obj: - curr_time = curr_date_obj - while curr_time < curr_date_obj + timedelta(days=1): - key = (curr_time.strftime("%Y-%m-%d %H:%M:%S"), TZ_OFFSET) - - # Update bpm, hrv, and spo2 every 10 seconds - bpm_mean_reversion = ( - target_mean_bpm - bpm_current - ) * 0.01 # Changed from 0.1 - bpm_current += bpm_mean_reversion + gaussian_noise_bpm() - bpm_current = max(bpm_lower_bound, min(bpm_upper_bound, bpm_current)) - bpm[key] = int(bpm_current) - - hrv_mean_reversion = (target_mean_hrv - hrv_current) * 0.1 - hrv_current += hrv_mean_reversion + gaussian_noise_hrv() - hrv_current = max(hrv_lower_bound, min(hrv_upper_bound, hrv_current)) - hrv[key] = int(hrv_current) - - spo2_mean_reversion = (target_mean_spo2 - spo2_current) * 0.1 - spo2_current += spo2_mean_reversion + gaussian_noise_spo2() - spo2_current = max( - spo2_lower_bound, min(spo2_upper_bound, spo2_current) - ) - spo2[key] = int(spo2_current) - - # Update brpm every minute - if time_index % 60 == 0: - brpm_mean_reversion = (target_mean_brpm - brpm_current) * 0.1 - brpm_current += brpm_mean_reversion + gaussian_noise_brpm() - brpm_current = max( - brpm_lower_bound, min(brpm_upper_bound, brpm_current) - ) - brpm[key] = int(brpm_current) - - # Increment time and index - curr_time += timedelta(seconds=10) - time_index += 10 - - curr_date_obj += timedelta(days=1) - - return bpm, brpm, hrv, spo2 - - def synthetic_steps_distance_per_minute(bpm_dict): - steps_dict = {} - distance_dict = {} - - # For each minute, we'll check the BPM to determine steps - for key, bpm_value in bpm_dict.items(): - dt, _ = key - - # Extracting hour to check for sleeping hours - curr_hour = int(dt.split(" ")[1].split(":")[0]) - - if dt.endswith("00"): # Checking if it's on a per-minute mark - if 23 <= curr_hour or curr_hour < 6: # typical sleeping hours - steps = random.choice( - [0, 0, 0, 0, 1, 2] - ) # Mostly zero, but sometimes a small number indicating tossing/turning in sleep - elif bpm_value < 60: - steps = random.randint(0, 20) # Relatively calm/resting - elif bpm_value < 80: - steps = random.randint(20, 40) # Maybe just light walking - else: - steps = random.randint(40, 120) # Active movement or jogging - - distance = steps * random.uniform(0.7, 0.8) - date_str = dt.split()[0] - time_str = dt.split()[1] - - steps_dict[f"{date_str} {time_str}"] = steps - distance_dict[f"{date_str} {time_str}"] = distance - - return steps_dict, distance_dict - - def synthetic_daily_calories(bpm_dict, steps_dict): - rest_cals_dict = {} - work_cals_dict = {} - active_cals_dict = {} - step_cals_dict = {} - total_cals_dict = {} - - daily_bpm_averages = {} - - # Precompute average BPM per day - for (dt, _), v in bpm_dict.items(): - date_str = dt.split(" ")[0] - daily_bpm_averages.setdefault(date_str, []).append(v) - - # Compute average BPM for each day - for date_str, bpm_vals in daily_bpm_averages.items(): - avg_bpm = sum(bpm_vals) / len(bpm_vals) - daily_bpm_averages[date_str] = avg_bpm - - # Calculate calories based on the precomputed average BPMs - for date_str, avg_bpm in daily_bpm_averages.items(): - if avg_bpm < 60: - active_cals = random.randint(50, 100) # relatively inactive - elif avg_bpm < 80: - active_cals = random.randint(100, 200) # moderately active - else: - active_cals = random.randint(200, 300) # very active - - steps_val = sum( - [val for dt, val in steps_dict.items() if dt.startswith(date_str)] - ) - - rest_cals_dict[date_str] = random.randint(1000, 1300) - work_cals_dict[date_str] = random.randint(300, 600) - step_cals_dict[date_str] = steps_val * 0.05 - active_cals_dict[date_str] = active_cals - total_cals_dict[date_str] = ( - rest_cals_dict[date_str] - + work_cals_dict[date_str] - + step_cals_dict[date_str] - + active_cals_dict[date_str] - ) - - return ( - rest_cals_dict, - work_cals_dict, - active_cals_dict, - step_cals_dict, - total_cals_dict, - ) - - def synthetic_activity(): - # A sample workout - activity_date = (start_date_obj + (end_date_obj - start_date_obj) / 2).strftime( - "%Y-%m-%d" - ) # choose a day in the middle of the range - - return { - "activity_date": activity_date, - "type": "Running", - "duration": timedelta(minutes=random.randint(20, 60)), - "distance": random.uniform(3, 10), - "calories_burned": random.randint(200, 500), - "avg_bpm": random.randint(80, 150), - "peak_bpm": random.randint(150, 180), - "steps_taken": random.randint(3000, 10000), - "intensity": random.choice(["light", "moderate", "high"]), - } - - def synthetic_sleep_session(bpm_dict): - # Extracting nighttime bpm readings - night_movements = { - k: v - for k, v in bpm_dict.items() - if 23 <= int(k[0].split(" ")[1].split(":")[0]) - or int(k[0].split(" ")[1].split(":")[0]) < 6 - } - # Filter out readings where bpm indicates deep sleep (low bpm) - night_movements = {k: v for k, v in night_movements.items() if v > 65} - return night_movements - - def synthetic_sleep_detail(): - sleep_date = (start_date_obj + (end_date_obj - start_date_obj) / 2).strftime( - "%Y-%m-%d" - ) - total_sleep_duration = 8 # Assuming 8 hours sleep - - light_sleep = random.uniform(0.4, 0.6) * total_sleep_duration - deep_sleep = random.uniform(0.2, 0.3) * total_sleep_duration - rem_sleep = total_sleep_duration - (light_sleep + deep_sleep) - - return { - "sleep_date": sleep_date, - "light_sleep": light_sleep, - "deep_sleep": deep_sleep, - "rem_sleep": rem_sleep, - "awake_time": random.uniform(0.1, 0.3), - "times_awoken": random.randint(1, 5), - } - - # Generate biometric, steps, and distance data - bpm, brpm, hrv, spo2 = synthetic_biometrics(start_date_obj, end_date_obj) - steps, distance = synthetic_steps_distance_per_minute(bpm) - - # Generate daily calories based on steps and bpm - rest_cals, work_cals, active_cals, step_cals, total_cals = synthetic_daily_calories( - bpm, steps - ) - - # Generate activity data - activities = synthetic_activity() - - # Generate sleep session data - sleep_session = synthetic_sleep_session(bpm) - - # Generate sleep detail data - sleep_detail = synthetic_sleep_detail() - - return ( - activities, - bpm, - brpm, - hrv, - spo2, - rest_cals, - work_cals, - active_cals, - step_cals, - total_cals, - sleep_session, - sleep_detail, - steps, - distance, - ) +import os +import pandas as pd + +def create_syn_data(survey): + script_dir = os.path.dirname(__file__) + survey_file = os.path.join(script_dir, "sample_survey.csv") + df = pd.read_csv(survey_file) + return df diff --git a/wearipedia/devices/qualtrics/sample_survey.csv b/wearipedia/devices/qualtrics/sample_survey.csv new file mode 100644 index 00000000..11408d50 --- /dev/null +++ b/wearipedia/devices/qualtrics/sample_survey.csv @@ -0,0 +1,24 @@ +StartDate,EndDate,Status,IPAddress,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,RecipientLastName,RecipientFirstName,RecipientEmail,ExternalReference,LocationLatitude,LocationLongitude,DistributionChannel,UserLanguage,Q1 Single choice,Q2 Multiple choice_1,Q2 Multiple choice_2,Q2 Multiple choice_3,Q2 Multiple choice_4,Q2 Multiple choice_5,Q2 Multiple choice_6,Q3 Dropdown list,Q4 Certified,Q5 Random choice,Q6 Text entry,Q7 Multi line text,Q8 Form field_1,Q8 Form field_2,Q8 Form field_3,Q8 Form field_4,Q9 Rank order_1,Q9 Rank order_2,Q9 Rank order_3,Q9 Rank order_4,Q9 Rank order_5,Q10 Matrix table_1,Q10 Matrix table_2,Q10 Matrix table_3,Q11 Slider_1,Q11 Slider_2,Q11 Slider_3,Q12 Side by side#1_1,Q12 Side by side#1_2,Q12 Side by side#1_3,Q12 Side by side#1_4,Q12 Side by side#1_5,Q12 Side by side#1_6,Q12 Side by side#1_7,Q12 Side by side#1_8,Q12 Side by side#2_1,Q12 Side by side#2_2,Q12 Side by side#2_3,Q12 Side by side#2_4,Q12 Side by side#2_5,Q12 Side by side#2_6,Q12 Side by side#2_7,Q12 Side by side#2_8,Q13 End of survey +Start Date,End Date,Response Type,IP Address,Progress,Duration (in seconds),Finished,Recorded Date,Response ID,Recipient Last Name,Recipient First Name,Recipient Email,External Data Reference,Location Latitude,Location Longitude,Distribution Channel,User Language,Did you find this explanation useful?,Why did you choose to take the question tour? - To learn about different question types and how to use them,Why did you choose to take the question tour? - To see best practices in survey construction and design,Why did you choose to take the question tour? - To explore the different features and functionality of the survey editor tool,Why did you choose to take the question tour? - To practice building and editing a survey without starting from scratch,Why did you choose to take the question tour? - To see if Qualtrics provides the features I need to build the survey I have planned,"Why did you choose to take the question tour? - None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",The question tour has helped me learn how to configure my own questions in Qualtrics.,How old are you?,Which type of multiple-choice question is easiest to use?,What kind of survey template would be most helpful in building a new survey for your needs?,Please provide your feedback on the content included in the question tour.,Name and contact information - First name,Name and contact information - Last name,Name and contact information - Email address,Name and contact information - Phone number (U.S.),Please rank the following question types in order of usefulness. - Single-answer multiple choice questions,Please rank the following question types in order of usefulness. - Multiple-answer multiple choice questions,Please rank the following question types in order of usefulness. - Single-line text entry questions,Please rank the following question types in order of usefulness. - Essay box text entry questions,Please rank the following question types in order of usefulness. - Rank order questions,Please rate the following in terms of how much you agree or disagree with each statement. - I can build the kind of questions necessary to collect the feedback I need using Qualtrics,Please rate the following in terms of how much you agree or disagree with each statement. - Qualtrics provides a flexible survey tool that can be configured to meet my needs,Please rate the following in terms of how much you agree or disagree with each statement. - I can use a variety of question types to accomplish my research goals,Please rate the question tour according to the following attributes. - Length,Please rate the question tour according to the following attributes. - Level of detail,Please rate the question tour according to the following attributes. - Range of content,How would you rate each of the following question types? - Ease of use - Multiple choice,How would you rate each of the following question types? - Ease of use - Text entry,How would you rate each of the following question types? - Ease of use - Text / Graphic,How would you rate each of the following question types? - Ease of use - Matrix table,How would you rate each of the following question types? - Ease of use - Slider,How would you rate each of the following question types? - Ease of use - Form field,How would you rate each of the following question types? - Ease of use - Rank order,How would you rate each of the following question types? - Ease of use - Side by side,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Multiple choice,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Text entry,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Text / Graphic,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Matrix table,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Slider,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Form field,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Rank order,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Side by side,End of survey summary +"{""ImportId"":""startDate"",""timeZone"":""Z""}","{""ImportId"":""endDate"",""timeZone"":""Z""}","{""ImportId"":""status""}","{""ImportId"":""ipAddress""}","{""ImportId"":""progress""}","{""ImportId"":""duration""}","{""ImportId"":""finished""}","{""ImportId"":""recordedDate"",""timeZone"":""Z""}","{""ImportId"":""_recordId""}","{""ImportId"":""recipientLastName""}","{""ImportId"":""recipientFirstName""}","{""ImportId"":""recipientEmail""}","{""ImportId"":""externalDataReference""}","{""ImportId"":""locationLatitude""}","{""ImportId"":""locationLongitude""}","{""ImportId"":""distributionChannel""}","{""ImportId"":""userLanguage""}","{""ImportId"":""QID4""}","{""ImportId"":""QID5"",""choiceId"":""1""}","{""ImportId"":""QID5"",""choiceId"":""2""}","{""ImportId"":""QID5"",""choiceId"":""3""}","{""ImportId"":""QID5"",""choiceId"":""4""}","{""ImportId"":""QID5"",""choiceId"":""5""}","{""ImportId"":""QID5"",""choiceId"":""6""}","{""ImportId"":""QID6""}","{""ImportId"":""QID16""}","{""ImportId"":""QID7""}","{""ImportId"":""QID15_TEXT""}","{""ImportId"":""QID8_TEXT""}","{""ImportId"":""QID9_1""}","{""ImportId"":""QID9_2""}","{""ImportId"":""QID9_3""}","{""ImportId"":""QID9_4""}","{""ImportId"":""QID10_1""}","{""ImportId"":""QID10_2""}","{""ImportId"":""QID10_3""}","{""ImportId"":""QID10_4""}","{""ImportId"":""QID10_5""}","{""ImportId"":""QID11_1""}","{""ImportId"":""QID11_2""}","{""ImportId"":""QID11_3""}","{""ImportId"":""QID12_1""}","{""ImportId"":""QID12_2""}","{""ImportId"":""QID12_3""}","{""ImportId"":""QID13#1_1""}","{""ImportId"":""QID13#1_2""}","{""ImportId"":""QID13#1_3""}","{""ImportId"":""QID13#1_4""}","{""ImportId"":""QID13#1_5""}","{""ImportId"":""QID13#1_6""}","{""ImportId"":""QID13#1_7""}","{""ImportId"":""QID13#1_8""}","{""ImportId"":""QID13#2_1""}","{""ImportId"":""QID13#2_2""}","{""ImportId"":""QID13#2_3""}","{""ImportId"":""QID13#2_4""}","{""ImportId"":""QID13#2_5""}","{""ImportId"":""QID13#2_6""}","{""ImportId"":""QID13#2_7""}","{""ImportId"":""QID13#2_8""}","{""ImportId"":""QID18""}" +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,84,True,2024-01-21 01:37:24,R_07F4QiGAgudB3GS,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,55-64 years old,Single-answer multiple choice questions,,,Wayne,Ross,Wayne.Ross@fakeemail.com,521-231-1500,1,2,5,4,3,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Not sure,Yes,Yes,Not sure,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,112,True,2024-01-21 01:37:24,R_0oKgKIRCjSrn5Cm,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,35-44 years old,Single-answer multiple choice questions,healthcare,I thought this would have more information on how to use Qualtrics instead of just showing me questions I can easily build myself or see in any template that is already built. It would be more useful to demonstrate how to use advanced features and spend less time focusing on basics that are easy for anybody to learn by doing.,Robert,Williams,Robert.Williams@fakeemail.com,521-231-2500,2,3,4,5,1,Somewhat agree,Somewhat agree,Strongly agree,4,4,3,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,99,True,2024-01-21 01:37:24,R_2rVJszl5QQXpWaq,,,,,,,preview,EN,Neither useful nor not useful,To learn about different question types and how to use them,To see best practices in survey construction and design,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Neither agree nor disagree,65+ years old,Multiple-answer multiple choice questions,You already have the templates I need!,This is a good starting point but it would be great if there was more content on methodology! I like the different examples and notes about improving the survey-takers' experiences,Judith,Peterson,Judith.Peterson@fakeemail.com,521-231-7700,1,2,3,4,5,Somewhat agree,Neither agree nor disagree,Somewhat agree,3,4,3,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Yes,Yes,Yes,Yes,No,No,Yes,Yes,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,123,True,2024-01-21 01:37:24,R_3HIq5UEnwg8TaES,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,,,Strongly agree,35-44 years old,Single-answer multiple choice questions,,,Eric,Wright,Eric.Wright@fakeemail.com,521-231-5400,2,3,5,4,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Very easy,Neither easy nor difficult,Yes,Yes,Yes,Yes,No,Not sure,Yes,Yes,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,120,True,2024-01-21 01:37:24,R_3I7GkdZfeYy8QOa,,,,,,,preview,EN,Neither useful nor not useful,,,,,,"None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",Neither agree nor disagree,55-64 years old,Single-answer multiple choice questions,,,Hannah,Kelly,Hannah.Kelly@fakeemail.com,521-231-7500,1,2,5,4,3,Somewhat agree,Somewhat disagree,Neither agree nor disagree,3,3,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Yes,Yes,Yes,Yes,No,Yes,Yes,Need more information,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_3l4IJE1mD4Q0EKi,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,45-54 years old,Single-answer multiple choice questions,csat,the content is pretty good but I wish there was more information on complex configurations,Alice,Wood,Alice.Wood@fakeemail.com,521-231-5600,2,3,4,1,5,Strongly agree,Strongly agree,Somewhat agree,5,4,5,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Not sure,Yes,Yes,Yes,No,Yes,Need more information,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,93,True,2024-01-21 01:37:24,R_57KVKagvu2wgMOW,,,,,,,preview,EN,Useful,,To see best practices in survey construction and design,,,,,Somewhat agree,25-34 years old,Select box multiple choice questions,,I would like to learn more about different research types and what questions work best for those populations,Katherine,Hill,Katherine.Hill@fakeemail.com,521-231-7500,1,2,3,4,5,Strongly agree,Somewhat agree,Somewhat agree,4,4,4,Somewhat easy,Neither easy nor difficult,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Somewhat easy,Somewhat easy,Yes,Yes,Yes,Yes,Yes,No,Yes,No,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,119,True,2024-01-21 01:37:24,R_6WirOjrtq9pdD2S,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,18-24 years old,Single-answer multiple choice questions,product feedback,more content on the more advanced and complex question types - there is a lot of really useful information about basic questions (multiple choice) and less detail about complex questions types (side-by-side) and it would be helpful to understand when those would be best used and how to set them up in the tool.,Julie,Stewart,Julie.Stewart@fakeemail.com,521-231-8500,1,2,4,5,3,Strongly agree,Strongly agree,Somewhat agree,4,4,4,Very easy,Very easy,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Very easy,Somewhat difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,79,True,2024-01-21 01:37:24,R_8H3GCq0wtaNCfKm,,,,,,,preview,EN,Not useful,,,,To practice building and editing a survey without starting from scratch,,,Somewhat disagree,45-54 years old,Single-answer multiple choice questions,,,Catherine,Roberts,Catherine.Roberts@fakeemail.com,521-231-5000,1,2,4,5,3,Neither agree nor disagree,Somewhat disagree,Neither agree nor disagree,3,3,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Yes,Not sure,Not sure,Yes,Not sure,Not sure,Yes,No,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,75,True,2024-01-21 01:37:24,R_aiaBjSWugjTnXgO,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,25-34 years old,Single-answer multiple choice questions,"event planning, event feedback",I think this is really helpful and I like the different examples that illustrate how different question types work.,Brian,Gonzalez,Brian.Gonzalez@fakeemail.com,521-231-5500,3,4,5,1,2,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_bf41KXAuaRyVP9A,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,18-24 years old,Single-answer multiple choice questions,,,Diane,Edwards,Diane.Edwards@fakeemail.com,521-231-6500,1,2,4,3,5,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Yes,Not sure,Yes,No,Not sure,Yes,No,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,139,True,2024-01-21 01:37:24,R_d3Zr8ngGxJ85rF4,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,To see best practices in survey construction and design,,,,,Strongly agree,45-54 years old,Single-answer multiple choice questions,,I would like more information about conditional questions and survey flow,Joyce,Flores,Joyce.Flores@fakeemail.com,521-231-3000,1,2,5,4,3,Somewhat agree,Strongly agree,Strongly agree,4,4,4,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Neither easy nor difficult,Somewhat easy,Somewhat easy,Yes,Not sure,Yes,Yes,Not sure,No,Yes,Need more information,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,120,True,2024-01-21 01:37:24,R_ddwpvVglXabjWm2,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,35-44 years old,Single-answer multiple choice questions,,,Jose,Edwards,Jose.Edwards@fakeemail.com,521-231-8100,3,4,5,2,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,189,True,2024-01-21 01:37:24,R_di2gEC0rP1J0NzE,,,,,,,preview,EN,Neither useful nor not useful,,,,To practice building and editing a survey without starting from scratch,,,Neither agree nor disagree,65+ years old,Dropdown list multiple choice questions,"product feedback, website feedback",this should be more interactive and include other features,Jerry,Torres,Jerry.Torres@fakeemail.com,521-231-5000,2,5,3,1,4,Somewhat agree,Somewhat disagree,Neither agree nor disagree,3,2,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Somewhat difficult,Neither easy nor difficult,Very difficult,Yes,Yes,Yes,Yes,No,Not sure,Yes,Need more information,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,124,True,2024-01-21 01:37:24,R_e3Ot9ljVM4VVObs,,,,,,,preview,EN,Not useful,,,,,,"None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",Somewhat disagree,65+ years old,Dropdown list multiple choice questions,,,Evelyn,Rogers,Evelyn.Rogers@fakeemail.com,521-231-6700,1,2,5,3,4,Neither agree nor disagree,Somewhat disagree,Somewhat disagree,3,2,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,No +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,116,True,2024-01-21 01:37:24,R_enh6S6dzmkN04zY,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,To practice building and editing a survey without starting from scratch,,,Strongly agree,18-24 years old,Single-answer multiple choice questions,,,Philip,Perry,Philip.Perry@fakeemail.com,521-231-5200,1,2,5,4,3,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat difficult,Somewhat easy,Very difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Need more information,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,104,True,2024-01-21 01:37:24,R_1YSV94HnpeUAHpY,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,18-24 years old,Single-answer multiple choice questions,,Really useful to see how different question types work. Especially like the comments on best practices and how to optimize for mobile - would like even more of that type of content.,Christina,Murphy,Christina.Murphy@fakeemail.com,521-231-7900,2,1,3,4,5,Strongly agree,Strongly agree,Strongly agree,5,5,4,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Yes,Yes,Yes,No,No,Yes,No,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_3HJUaSjRfCBgCH4,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,,,Strongly agree,35-44 years old,Single-answer multiple choice questions,NPS,I feel like it's pretty long but also has been useful,Billy,Gray,Billy.Gray@fakeemail.com,521-231-9100,2,3,5,4,1,Strongly agree,Strongly agree,Somewhat agree,4,5,5,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Very easy,Somewhat difficult,Yes,Yes,Yes,Yes,Not sure,Yes,Yes,Need more information,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,77,True,2024-01-21 01:37:24,R_3vXXb7tv4uogYM6,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,,,Strongly agree,45-54 years old,Single-answer multiple choice questions,,,Emma,Ramirez,Emma.Ramirez@fakeemail.com,521-231-6500,3,2,5,4,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Yes,Not sure,Not sure,Not sure,Yes +2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,630,True,2024-01-21 01:37:24,R_cZPXTgwJn4BL5rw,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,,,Strongly agree,25-34 years old,Single-answer multiple choice questions,polls,I thought there would be more information on the survey builder tool and how to use it,Sara,Brooks,Sara.Brooks@fakeemail.com,521-231-4500,1,2,4,5,3,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,No,Yes,Yes,Not sure,No,Yes,Need more information,Yes +2024-02-02 12:17:53,2024-02-03 08:40:08,IP Address,104.28.124.180,73,73335,False,2024-02-10 08:40:09,R_62LwG6itKcvqz6j,,,,,,,anonymous,EN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, From 09ea0e443745e0f9e0c5827474d4d2e55da60e54 Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Mon, 27 May 2024 18:22:05 -0700 Subject: [PATCH 4/8] Jack's comments addressed --- wearipedia/devices/qualtrics/qualtrics.py | 56 ++++++++++++++++++- .../devices/qualtrics/qualtrics_fetch.py | 12 ++++ wearipedia/devices/qualtrics/qualtrics_gen.py | 13 +++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/wearipedia/devices/qualtrics/qualtrics.py b/wearipedia/devices/qualtrics/qualtrics.py index 9e4a03ed..d1fcd068 100644 --- a/wearipedia/devices/qualtrics/qualtrics.py +++ b/wearipedia/devices/qualtrics/qualtrics.py @@ -8,7 +8,21 @@ class_name = "Qualtrics" class Qualtrics(BaseDevice): - def __init__(self, seed=0, survey="SV_6FDXhJO8mupUnuC"): + """ + A class representing a Qualtrics survey device. + + This class is responsible for initializing the device with the necessary + parameters, generating synthetic data, and fetching real data from Qualtrics + using the unofficial QualtricsAPI. + """ + def __init__(self, seed=0, survey="your_survey_id_here"): + """ + Initializes the Qualtrics device with the given seed and survey ID from Qualtrics. + + Parameters: + seed (int, optional): The seed for random number generation. + survey (str, optional): The ID of the survey to be used. Retrieve this from Qualtrics. + """ params = { "seed": seed, "survey": str(survey), @@ -26,25 +40,65 @@ def __init__(self, seed=0, survey="SV_6FDXhJO8mupUnuC"): ) def _default_params(self): + """ + Returns the default parameters for the Qualtrics device. + + Returns: + dict: A dictionary containing the default parameters. + """ return { "survey": self.init_params["synthetic_survey"], } def _get_real(self, data_type, params): + """ + Fetches real data from Qualtrics based on the provided parameters. + + Parameters: + data_type (str): The type of data to fetch. + params (dict): A dictionary containing the parameters for fetching data. + + Returns: + dict: A dictionary containing the fetched real data. + """ return fetch_real_data( params["survey"] ) def _filter_synthetic(self, data, data_type, params): + """ + Filters the synthetic data based on the provided parameters. This method is not implemented. + + Parameters: + data (dict): The synthetic data to filter. + data_type (str): The type of data being filtered. + params (dict): A dictionary containing the parameters for filtering data. + + Returns: + dict: The filtered synthetic data. This method returns the data as is. + """ return data def _gen_synthetic(self): + """ + Generates synthetic survey data based on the initial parameters. + + This method sets the `responses` attribute with the generated synthetic data. + """ seed_everything(self.init_params["seed"]) self.responses = create_syn_data( self.init_params["synthetic_survey"], ) def _authenticate(self, auth_creds): + """ + Authenticates the device using the provided credentials. + + Parameters: + auth_creds (dict): A dictionary containing authentication credentials. + - token (str): The API token for authentication. + - data_center (str): The data center to connect to. + """ token = auth_creds["token"] data_center = auth_creds["data_center"] Credentials().qualtrics_api_credentials(token=token, data_center=data_center) diff --git a/wearipedia/devices/qualtrics/qualtrics_fetch.py b/wearipedia/devices/qualtrics/qualtrics_fetch.py index 8a60f05c..e50a563f 100644 --- a/wearipedia/devices/qualtrics/qualtrics_fetch.py +++ b/wearipedia/devices/qualtrics/qualtrics_fetch.py @@ -2,5 +2,17 @@ from QualtricsAPI.Setup import Credentials def fetch_real_data(survey): + """ + Fetches real survey data from Qualtrics. + + This function uses QualtricsAPI to fetch survey responses based on the given survey ID. + The responses are retrieved with labels (actual user responses) instead of numeric codes. + + Parameters: + survey (str): The ID of the survey from which to fetch responses. + + Returns: + dict: A dictionary containing the survey responses. + """ responses = Responses().get_survey_responses(survey=survey, useLabels=True) return responses diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 31feaa3e..9bd1a6ce 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -2,6 +2,19 @@ import pandas as pd def create_syn_data(survey): + """ + Creates synthetic survey data based on a sample survey CSV file. + + This function reads a sample survey CSV file located in the same directory as the script + and returns its contents as a Pandas DataFrame. The function assumes that the sample survey + CSV file is named "sample_survey.csv". + + Parameters: + survey (str): The ID of the survey to be used. This parameter is currently not used in the function. + + Returns: + pd.DataFrame: A DataFrame containing the synthetic survey data from the sample CSV file. + """ script_dir = os.path.dirname(__file__) survey_file = os.path.join(script_dir, "sample_survey.csv") df = pd.read_csv(survey_file) From 1076c009916f0f9d71cb2a3deefe9d7675abe6fc Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Thu, 4 Jul 2024 14:59:39 -0700 Subject: [PATCH 5/8] Documentation modified in line with format --- wearipedia/devices/qualtrics/qualtrics.py | 59 +++---------------- .../devices/qualtrics/qualtrics_fetch.py | 9 ++- wearipedia/devices/qualtrics/qualtrics_gen.py | 9 +-- 3 files changed, 15 insertions(+), 62 deletions(-) diff --git a/wearipedia/devices/qualtrics/qualtrics.py b/wearipedia/devices/qualtrics/qualtrics.py index d1fcd068..f66b0087 100644 --- a/wearipedia/devices/qualtrics/qualtrics.py +++ b/wearipedia/devices/qualtrics/qualtrics.py @@ -9,20 +9,17 @@ class Qualtrics(BaseDevice): """ - A class representing a Qualtrics survey device. + This device allows you to work with data from the Qualtrics survey platform. + Available datatypes for this device are: - This class is responsible for initializing the device with the necessary - parameters, generating synthetic data, and fetching real data from Qualtrics - using the unofficial QualtricsAPI. + * `responses`: a DataFrame of survey responses + + :param seed: random seed for synthetic data generation, defaults to 0 + :type seed: int, optional + :param survey: ID of the survey, required + :type survey: str """ def __init__(self, seed=0, survey="your_survey_id_here"): - """ - Initializes the Qualtrics device with the given seed and survey ID from Qualtrics. - - Parameters: - seed (int, optional): The seed for random number generation. - survey (str, optional): The ID of the survey to be used. Retrieve this from Qualtrics. - """ params = { "seed": seed, "survey": str(survey), @@ -40,65 +37,25 @@ def __init__(self, seed=0, survey="your_survey_id_here"): ) def _default_params(self): - """ - Returns the default parameters for the Qualtrics device. - - Returns: - dict: A dictionary containing the default parameters. - """ return { "survey": self.init_params["synthetic_survey"], } def _get_real(self, data_type, params): - """ - Fetches real data from Qualtrics based on the provided parameters. - - Parameters: - data_type (str): The type of data to fetch. - params (dict): A dictionary containing the parameters for fetching data. - - Returns: - dict: A dictionary containing the fetched real data. - """ return fetch_real_data( params["survey"] ) def _filter_synthetic(self, data, data_type, params): - """ - Filters the synthetic data based on the provided parameters. This method is not implemented. - - Parameters: - data (dict): The synthetic data to filter. - data_type (str): The type of data being filtered. - params (dict): A dictionary containing the parameters for filtering data. - - Returns: - dict: The filtered synthetic data. This method returns the data as is. - """ return data def _gen_synthetic(self): - """ - Generates synthetic survey data based on the initial parameters. - - This method sets the `responses` attribute with the generated synthetic data. - """ seed_everything(self.init_params["seed"]) self.responses = create_syn_data( self.init_params["synthetic_survey"], ) def _authenticate(self, auth_creds): - """ - Authenticates the device using the provided credentials. - - Parameters: - auth_creds (dict): A dictionary containing authentication credentials. - - token (str): The API token for authentication. - - data_center (str): The data center to connect to. - """ token = auth_creds["token"] data_center = auth_creds["data_center"] Credentials().qualtrics_api_credentials(token=token, data_center=data_center) diff --git a/wearipedia/devices/qualtrics/qualtrics_fetch.py b/wearipedia/devices/qualtrics/qualtrics_fetch.py index e50a563f..e1b2f5e4 100644 --- a/wearipedia/devices/qualtrics/qualtrics_fetch.py +++ b/wearipedia/devices/qualtrics/qualtrics_fetch.py @@ -4,15 +4,14 @@ def fetch_real_data(survey): """ Fetches real survey data from Qualtrics. - This function uses QualtricsAPI to fetch survey responses based on the given survey ID. The responses are retrieved with labels (actual user responses) instead of numeric codes. - Parameters: - survey (str): The ID of the survey from which to fetch responses. + :param survey: the ID of the survey from which to fetch responses + :type start_date: str - Returns: - dict: A dictionary containing the survey responses. + :return: the survey responses fetched from the API + :rtype: Pandas DataFrame """ responses = Responses().get_survey_responses(survey=survey, useLabels=True) return responses diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 9bd1a6ce..3e824a1a 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -1,7 +1,7 @@ import os import pandas as pd -def create_syn_data(survey): +def create_syn_data(): """ Creates synthetic survey data based on a sample survey CSV file. @@ -9,11 +9,8 @@ def create_syn_data(survey): and returns its contents as a Pandas DataFrame. The function assumes that the sample survey CSV file is named "sample_survey.csv". - Parameters: - survey (str): The ID of the survey to be used. This parameter is currently not used in the function. - - Returns: - pd.DataFrame: A DataFrame containing the synthetic survey data from the sample CSV file. + :return: A DataFrame containing the synthetic survey data from the sample CSV file. + :rtype: pd.DataFrame """ script_dir = os.path.dirname(__file__) survey_file = os.path.join(script_dir, "sample_survey.csv") From edeffda37a8e5d958e5aef2fb728c03e407ee0be Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Thu, 15 Aug 2024 05:11:15 -0700 Subject: [PATCH 6/8] Placeholder deleted, actual survey added --- .../devices/qualtrics/patient_intake.csv | 67 +++++++++++++++++++ .../devices/qualtrics/sample_survey.csv | 24 ------- 2 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 wearipedia/devices/qualtrics/patient_intake.csv delete mode 100644 wearipedia/devices/qualtrics/sample_survey.csv diff --git a/wearipedia/devices/qualtrics/patient_intake.csv b/wearipedia/devices/qualtrics/patient_intake.csv new file mode 100644 index 00000000..edd22a2d --- /dev/null +++ b/wearipedia/devices/qualtrics/patient_intake.csv @@ -0,0 +1,67 @@ +StartDate,EndDate,Status,IPAddress,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,RecipientLastName,RecipientFirstName,RecipientEmail,ExternalReference,LocationLatitude,LocationLongitude,DistributionChannel,UserLanguage,F_Name,L_Name,DOB#1_1,DOB#2_1,DOB#3_1,PHQ-9_1,PHQ-9_2,PHQ-9_3,PHQ-9_4,PHQ-9_5,PHQ-9_6,PHQ-9_7,PHQ-9_8,PHQ-9_9,PHQ-9_Add,GAD-7_1,GAD-7_2,GAD-7_3,GAD-7_4,GAD-7_5,GAD-7_6,GAD-7_7,GAD-7_Add,Add_Info +Start Date,End Date,Response Type,IP Address,Progress,Duration (in seconds),Finished,Recorded Date,Response ID,Recipient Last Name,Recipient First Name,Recipient Email,External Data Reference,Location Latitude,Location Longitude,Distribution Channel,User Language,1. What your legal first name?,2. What is your legal last name?,3. What is your legal date of birth? - Month - Please select,3. What is your legal date of birth? - Day - Please select,3. What is your legal date of birth? - Year - Please select,"PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Little interest or pleasure in doing things","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Feeling down, depressed, or hopeless","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Trouble falling or staying asleep, or sleeping too much","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Feeling tired or having little energy","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Poor appetite or overeating","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Feeling bad about yourself — or that you are a failure or have let yourself or your family down","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Trouble concentrating on things, such as reading the newspaper or watching television","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Moving or speaking so slowly that other people could have noticed? Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual","PHQ-9 + +4. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Thoughts that you would be better off dead or of hurting yourself in some way","4a. How difficult have these problems made it for you to do your +work, take care of things at home, or get along with other people?","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Feeling nervous, anxious, or on edge","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Not being able to stop or control worrying","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Worrying too much about different things","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Trouble relaxing","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Being so restless that it is hard to sit still","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Becoming easily annoyed or irritable","GAD-7 + +5. Over the last 2 weeks, how often have you been bothered by any of the following problems? - Feeling afraid, as if something awful might happen","5a. How difficult have these problems made it for you to do your +work, take care of things at home, or get along with other people?",6. Please describe any specific stressors or changes in your mental health that you think are important for us to know. +"{""ImportId"":""startDate"",""timeZone"":""Z""}","{""ImportId"":""endDate"",""timeZone"":""Z""}","{""ImportId"":""status""}","{""ImportId"":""ipAddress""}","{""ImportId"":""progress""}","{""ImportId"":""duration""}","{""ImportId"":""finished""}","{""ImportId"":""recordedDate"",""timeZone"":""Z""}","{""ImportId"":""_recordId""}","{""ImportId"":""recipientLastName""}","{""ImportId"":""recipientFirstName""}","{""ImportId"":""recipientEmail""}","{""ImportId"":""externalDataReference""}","{""ImportId"":""locationLatitude""}","{""ImportId"":""locationLongitude""}","{""ImportId"":""distributionChannel""}","{""ImportId"":""userLanguage""}","{""ImportId"":""QID6_TEXT""}","{""ImportId"":""QID7_TEXT""}","{""ImportId"":""QID1718155243#1_1""}","{""ImportId"":""QID1718155243#2_1""}","{""ImportId"":""QID1718155243#3_1""}","{""ImportId"":""QID4_1""}","{""ImportId"":""QID4_2""}","{""ImportId"":""QID4_3""}","{""ImportId"":""QID4_4""}","{""ImportId"":""QID4_5""}","{""ImportId"":""QID4_6""}","{""ImportId"":""QID4_7""}","{""ImportId"":""QID4_8""}","{""ImportId"":""QID4_9""}","{""ImportId"":""QID1""}","{""ImportId"":""QID27_1""}","{""ImportId"":""QID27_2""}","{""ImportId"":""QID27_3""}","{""ImportId"":""QID27_4""}","{""ImportId"":""QID27_5""}","{""ImportId"":""QID27_6""}","{""ImportId"":""QID27_7""}","{""ImportId"":""QID28""}","{""ImportId"":""QID30_TEXT""}" +2024-08-15 03:03:10,2024-08-15 03:04:52,Survey Preview,,100,101,True,2024-08-15 03:04:52,R_7l6ochnzHTcEZbj,,,,,37.423,-122.1639,preview,EN,Max,Lee,February,4,2002,More than half the days,More than half the days,More than half the days,Several days,Several days,Several days,Several days,More than half the days,Nearly every day,Somewhat difficult,More than half the days,More than half the days,Several days,Nearly every day,More than half the days,More than half the days,Nearly every day,Very difficult,Feeling isolated and anxious about job security. The prolonged quarantine has affected my mental health and financial stability. +2024-08-15 03:07:14,2024-08-15 03:08:14,Survey Preview,,100,59,True,2024-08-15 03:08:14,R_71hZi1jox9IZGlL,,,,,37.423,-122.1639,preview,EN,Jennifer,Gomez,October,17,1994,More than half the days,More than half the days,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,More than half the days,Nearly every day,Very difficult,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,Very difficult,I lost my job due to COVID-19. Struggling to find a new one. The uncertainty is overwhelming. +2024-08-15 03:08:20,2024-08-15 03:09:25,Survey Preview,,100,64,True,2024-08-15 03:09:25,R_7yefcEdN5mVMDSB,,,,,37.423,-122.1639,preview,EN,Danny,Du,November,29,1981,Several days,Several days,More than half the days,More than half the days,Nearly every day,More than half the days,Several days,Several days,More than half the days,Somewhat difficult,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Somewhat difficult,The pandemic has made me feel trapped at home. I'm constantly worried about my family's health and the future. +2024-08-15 03:09:59,2024-08-15 03:17:38,Survey Preview,,100,458,True,2024-08-15 03:17:38,R_72mVA69AsT1F7GV,,,,,37.423,-122.1639,preview,EN,Kian,McDonald,August,2,1963,More than half the days,More than half the days,Several days,Nearly every day,Nearly every day,More than half the days,More than half the days,Several days,Nearly every day,Very difficult,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,Nearly every day,Nearly every day,Very difficult,"The news is all bad. I've been laid off, and it's hard to stay positive. My anxiety is through the roof." +2024-08-15 03:17:49,2024-08-15 03:19:41,Survey Preview,,100,111,True,2024-08-15 03:19:42,R_7EhYg3iglUgedFA,,,,,37.423,-122.1639,preview,EN,Tatsumoto,Hashinori,June,28,1984,Nearly every day,More than half the days,Several days,More than half the days,More than half the days,Several days,Nearly every day,More than half the days,More than half the days,Somewhat difficult,More than half the days,Nearly every day,More than half the days,Nearly every day,Nearly every day,Nearly every day,More than half the days,Somewhat difficult,COVID-19 has impacted my life in every way. I feel hopeless and anxious all the time. +2024-08-15 03:19:49,2024-08-15 03:21:44,Survey Preview,,100,115,True,2024-08-15 03:21:45,R_7OpEs8mpvSzExVa,,,,,37.423,-122.1639,preview,EN,Joshua,Devencenzi,February,14,1974,Nearly every day,More than half the days,More than half the days,More than half the days,Nearly every day,More than half the days,More than half the days,Nearly every day,Nearly every day,Somewhat difficult,Nearly every day,Nearly every day,Nearly every day,Nearly every day,More than half the days,Several days,Nearly every day,Very difficult,Quarantine is unbearable. I'm worried about paying bills and feeding my family. I feel depressed and anxious. +2024-08-15 03:21:51,2024-08-15 03:27:03,Survey Preview,,100,312,True,2024-08-15 03:27:04,R_7EmWYALrPuBfv8F,,,,,37.423,-122.1639,preview,EN,Maxmillain,Baldwin,December,27,1993,Nearly every day,Nearly every day,Several days,More than half the days,Several days,Several days,Nearly every day,More than half the days,More than half the days,Extremely difficult,Nearly every day,Nearly every day,Nearly every day,More than half the days,More than half the days,More than half the days,More than half the days,Somewhat difficult,The pandemic has increased my stress levels. I'm constantly worrying about my elderly parents and their health. +2024-08-15 03:27:20,2024-08-15 03:28:19,Survey Preview,,100,59,True,2024-08-15 03:28:20,R_7tc0HJB7suisDkt,,,,,37.423,-122.1639,preview,EN,Kaleb,Cowell,April,1,1990,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,Very difficult,More than half the days,More than half the days,More than half the days,Several days,More than half the days,Several days,More than half the days,Very difficult,Lost my part-time job due to COVID-19. It has left me feeling anxious and depressed about my future. +2024-08-15 03:28:23,2024-08-15 03:29:31,Survey Preview,,100,67,True,2024-08-15 03:29:31,R_7Pi0ivpc2CQBwlk,,,,,37.423,-122.1639,preview,EN,Alex,Wang,May,13,1977,More than half the days,Several days,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,Very difficult,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Very difficult,Isolation is getting to me. I miss social interactions and fear what will happen next. +2024-08-15 03:29:45,2024-08-15 03:32:44,Survey Preview,,100,178,True,2024-08-15 03:32:45,R_7YmiI28KKqOCoh5,,,,,37.423,-122.1639,preview,EN,Kelly,Choi,April,30,2001,Nearly every day,More than half the days,Nearly every day,More than half the days,More than half the days,More than half the days,More than half the days,Nearly every day,More than half the days,Somewhat difficult,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,Very difficult,The quarantine has amplified my anxiety and depression. I'm worried about my job and financial security. +2024-08-15 03:35:27,2024-08-15 03:37:12,Survey Preview,,100,105,True,2024-08-15 03:37:13,R_7k0W6GBkyzjulpr,,,,,37.423,-122.1639,preview,EN,Ivanka,Harris,September,16,1989,More than half the days,More than half the days,Nearly every day,Nearly every day,More than half the days,More than half the days,Nearly every day,Nearly every day,More than half the days,Very difficult,More than half the days,More than half the days,Nearly every day,More than half the days,Nearly every day,Nearly every day,Nearly every day,Very difficult,COVID-19 has disrupted my life completely. I feel overwhelmed and stressed about the future. +2024-08-15 03:37:16,2024-08-15 03:39:03,Survey Preview,,100,106,True,2024-08-15 03:39:03,R_7Kl762qhTk7MdcF,,,,,37.423,-122.1639,preview,EN,Melanie,McNair,April,19,1955,Nearly every day,Nearly every day,More than half the days,More than half the days,More than half the days,More than half the days,More than half the days,Nearly every day,More than half the days,Extremely difficult,Nearly every day,More than half the days,Nearly every day,Nearly every day,Nearly every day,Several days,Nearly every day,Somewhat difficult,I'm struggling to cope with the pandemic's impact on my life. Anxiety and depression are constant companions. +2024-08-15 03:39:07,2024-08-15 03:40:42,Survey Preview,,100,95,True,2024-08-15 03:40:42,R_7QDUWDTBwr9T7iq,,,,,37.423,-122.1639,preview,EN,Trish,Nguyen,October,10,1950,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,Nearly every day,More than half the days,More than half the days,Extremely difficult,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,Extremely difficult,Job loss and quarantine have left me feeling hopeless. I'm worried about everything. +2024-08-15 03:40:45,2024-08-15 03:42:09,Survey Preview,,100,83,True,2024-08-15 03:42:09,R_7usnR9nta5SY8LL,,,,,37.423,-122.1639,preview,EN,Jack,Singla,November,11,2005,Nearly every day,Nearly every day,More than half the days,More than half the days,Nearly every day,More than half the days,Nearly every day,Nearly every day,More than half the days,Very difficult,Nearly every day,Nearly every day,More than half the days,Nearly every day,Nearly every day,Nearly every day,More than half the days,Extremely difficult,The pandemic is a constant source of anxiety. I'm concerned about my health and my family's future. +2024-08-15 03:42:14,2024-08-15 03:44:31,Survey Preview,,100,136,True,2024-08-15 03:44:31,R_7S2ngSpFQLvsyye,,,,,37.423,-122.1639,preview,EN,Hana,Simons,June,13,2000,Nearly every day,Nearly every day,Nearly every day,Nearly every day,More than half the days,More than half the days,Nearly every day,Nearly every day,More than half the days,Extremely difficult,Nearly every day,Nearly every day,Nearly every day,More than half the days,Nearly every day,More than half the days,Nearly every day,Extremely difficult,Isolation has taken a toll on my mental health. Financial stress and fear of illness are overwhelming. +2024-08-15 03:44:44,2024-08-15 03:45:58,Survey Preview,,100,74,True,2024-08-15 03:45:59,R_7Jxviq4xCQKESlp,,,,,37.423,-122.1639,preview,EN,Briana,Merger,January,7,1959,Several days,Several days,Several days,Several days,Several days,Several days,Several days,Several days,Several days,Very difficult,Not at all,Not at all,Not at all,Several days,Several days,Several days,Several days,Somewhat difficult,"Feeling low and unmotivated. COVID-19 has made me reflect on my life choices, but I don't feel anxious." +2024-08-15 03:46:02,2024-08-15 03:46:51,Survey Preview,,100,49,True,2024-08-15 03:46:51,R_7pSpKAE9tR8Yi7z,,,,,37.423,-122.1639,preview,EN,Candy,Chen,August,28,1983,Not at all,Not at all,Several days,Several days,Several days,Not at all,Not at all,Not at all,Not at all,Not difficult at all,More than half the days,More than half the days,Several days,Several days,Several days,Several days,Several days,Somewhat difficult,"I'm constantly worried about catching COVID-19, but I'm not feeling particularly sad or depressed." +2024-08-15 03:48:49,2024-08-15 03:50:17,Survey Preview,,100,88,True,2024-08-15 03:50:18,R_7gQxYQZ4z1mrdlv,,,,,37.423,-122.1639,preview,EN,Nathan,Raj,March,25,1998,Several days,Several days,Several days,Several days,Several days,Several days,Several days,Not at all,Not at all,Somewhat difficult,More than half the days,Several days,Several days,Nearly every day,More than half the days,More than half the days,Several days,Very difficult,"The pandemic has made me anxious about health, but I can still find joy in daily activities." +2024-08-15 03:50:26,2024-08-15 03:51:46,Survey Preview,,100,79,True,2024-08-15 03:51:46,R_7qfvcCDoLaUTrQd,,,,,37.423,-122.1639,preview,EN,Qian,Liu,November,8,1950,Several days,More than half the days,Several days,More than half the days,More than half the days,Several days,Several days,More than half the days,More than half the days,Somewhat difficult,Several days,Several days,Not at all,Several days,Not at all,Several days,Not at all,Not difficult at all,"COVID-19 has left me feeling down and lethargic, but I'm not anxious about it." +2024-08-15 03:51:50,2024-08-15 03:53:07,Survey Preview,,100,76,True,2024-08-15 03:53:07,R_7n2Eytgn7wOUml0,,,,,37.423,-122.1639,preview,EN,Jake,Princeton,February,27,1985,Several days,Several days,Not at all,Not at all,Several days,Not at all,Not at all,Not at all,Not at all,Not difficult at all,Several days,Several days,Several days,Several days,Several days,Several days,Several days,Somewhat difficult,"I worry about my family's safety, but I'm not experiencing sadness or depression." +2024-08-15 03:53:12,2024-08-15 03:58:18,Survey Preview,,100,305,True,2024-08-15 03:58:18,R_790wCvDTw7xWpKF,,,,,37.423,-122.1639,preview,EN,Alexander,Huy,March,27,1981,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,,Not at all,Not at all,Not at all,Several days,Not at all,Not at all,Not at all,Not difficult at all,I sometimes think about ending it all. COVID-19 makes me feel like there's no point in living. I think I will jump off a building today. +2024-08-15 03:58:21,2024-08-15 04:00:48,Survey Preview,,100,146,True,2024-08-15 04:00:49,R_7fk5fWdndFGTpzI,,,,,37.423,-122.1639,preview,EN,Brandon,Robarts,December,31,1977,Several days,Several days,Several days,Not at all,Not at all,Not at all,Several days,Several days,Not at all,Somewhat difficult,Several days,Several days,Several days,Several days,Several days,Several days,Not at all,Somewhat difficult,Feeling really trapped that I kicked my cat. The pandemic has pushed me to the edge. +2024-08-15 04:00:52,2024-08-15 04:02:37,Survey Preview,,100,104,True,2024-08-15 04:02:38,R_7vkmNNY6wnAb20v,,,,,37.423,-122.1639,preview,EN,Joy,Tan-Levy,November,5,1953,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,,Sometimes I think about dangerous things. The quarantine makes me feel like nothing matters anymore. +2024-08-15 04:11:19,2024-08-15 04:12:55,Survey Preview,,100,96,True,2024-08-15 04:12:56,R_7tilrP2BYFtplra,,,,,37.423,-122.1639,preview,EN,Alice,Cayen,February,5,1999,Several days,Not at all,Several days,Several days,Several days,Several days,Not at all,Several days,Not at all,Not difficult at all,Several days,Not at all,Several days,Not at all,Several days,Not at all,Not at all,Somewhat difficult,"I'm managing my stress through exercise and meditation, but the pandemic still affects me." +2024-08-15 04:12:58,2024-08-15 04:14:37,Survey Preview,,100,98,True,2024-08-15 04:14:37,R_7qw3DkFis39P2yh,,,,,37.423,-122.1639,preview,EN,Jiyan,Quan,September,22,1986,Several days,Not at all,Several days,Not at all,Not at all,Several days,Several days,Not at all,Not at all,Somewhat difficult,Several days,Several days,Not at all,Not at all,Not at all,Not at all,Several days,Somewhat difficult,I've been connecting with friends virtually to keep my spirits up during quarantine. +2024-08-15 11:54:14,2024-08-15 11:57:37,Survey Preview,,100,202,True,2024-08-15 11:57:37,R_7J2stKdVtbcJh6x,,,,,37.423,-122.1639,preview,EN,Quora,Fox,September,21,1973,Several days,Several days,Not at all,Several days,Not at all,Several days,Several days,More than half the days,Not at all,Not difficult at all,Several days,Several days,Several days,Several days,Not at all,Not at all,Not at all,Somewhat difficult,I'm focused on maintaining a routine to manage anxiety and stay positive. +2024-08-15 11:57:43,2024-08-15 11:59:04,Survey Preview,,100,81,True,2024-08-15 11:59:05,R_7Q9UZ2SbC7hL7YR,,,,,37.423,-122.1639,preview,EN,Emma,Bladen,March,20,1983,Several days,Several days,Not at all,Not at all,Several days,Not at all,Several days,Several days,Several days,Somewhat difficult,Several days,Several days,Several days,Not at all,Several days,Several days,Several days,Somewhat difficult,I try to limit my news consumption to reduce stress about the pandemic. +2024-08-15 11:59:08,2024-08-15 12:00:34,Survey Preview,,100,85,True,2024-08-15 12:00:34,R_7DxV5LHSPlczl61,,,,,37.423,-122.1639,preview,EN,Ellen,Davidson,November,29,2002,Several days,Not at all,Several days,Not at all,Not at all,Not at all,Several days,Several days,Not at all,Somewhat difficult,Not at all,Not at all,Several days,Several days,Several days,Several days,Not at all,Somewhat difficult,Regular exercise and healthy eating have helped me cope with COVID-19 anxiety. +2024-08-15 12:00:39,2024-08-15 12:02:31,Survey Preview,,100,112,True,2024-08-15 12:02:32,R_7OjYQEl5ptT8iN4,,,,,37.423,-122.1639,preview,EN,Haydn,Earhart,February,17,1992,Not at all,Not at all,Several days,Several days,Not at all,Not at all,Several days,Several days,Several days,Somewhat difficult,Not at all,Several days,Not at all,Several days,Several days,Not at all,Several days,Not difficult at all,I've been focusing on self-care and staying connected with family to manage stress. +2024-08-15 12:02:39,2024-08-15 12:04:09,Survey Preview,,100,89,True,2024-08-15 12:04:09,R_7QSGJutYZKzDP2J,,,,,37.423,-122.1639,preview,EN,Jeffrey,Kim,October,30,1988,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,Not at all,,I'm at peace. I'm feeling pretty good even though the world is going through hard times. diff --git a/wearipedia/devices/qualtrics/sample_survey.csv b/wearipedia/devices/qualtrics/sample_survey.csv deleted file mode 100644 index 11408d50..00000000 --- a/wearipedia/devices/qualtrics/sample_survey.csv +++ /dev/null @@ -1,24 +0,0 @@ -StartDate,EndDate,Status,IPAddress,Progress,Duration (in seconds),Finished,RecordedDate,ResponseId,RecipientLastName,RecipientFirstName,RecipientEmail,ExternalReference,LocationLatitude,LocationLongitude,DistributionChannel,UserLanguage,Q1 Single choice,Q2 Multiple choice_1,Q2 Multiple choice_2,Q2 Multiple choice_3,Q2 Multiple choice_4,Q2 Multiple choice_5,Q2 Multiple choice_6,Q3 Dropdown list,Q4 Certified,Q5 Random choice,Q6 Text entry,Q7 Multi line text,Q8 Form field_1,Q8 Form field_2,Q8 Form field_3,Q8 Form field_4,Q9 Rank order_1,Q9 Rank order_2,Q9 Rank order_3,Q9 Rank order_4,Q9 Rank order_5,Q10 Matrix table_1,Q10 Matrix table_2,Q10 Matrix table_3,Q11 Slider_1,Q11 Slider_2,Q11 Slider_3,Q12 Side by side#1_1,Q12 Side by side#1_2,Q12 Side by side#1_3,Q12 Side by side#1_4,Q12 Side by side#1_5,Q12 Side by side#1_6,Q12 Side by side#1_7,Q12 Side by side#1_8,Q12 Side by side#2_1,Q12 Side by side#2_2,Q12 Side by side#2_3,Q12 Side by side#2_4,Q12 Side by side#2_5,Q12 Side by side#2_6,Q12 Side by side#2_7,Q12 Side by side#2_8,Q13 End of survey -Start Date,End Date,Response Type,IP Address,Progress,Duration (in seconds),Finished,Recorded Date,Response ID,Recipient Last Name,Recipient First Name,Recipient Email,External Data Reference,Location Latitude,Location Longitude,Distribution Channel,User Language,Did you find this explanation useful?,Why did you choose to take the question tour? - To learn about different question types and how to use them,Why did you choose to take the question tour? - To see best practices in survey construction and design,Why did you choose to take the question tour? - To explore the different features and functionality of the survey editor tool,Why did you choose to take the question tour? - To practice building and editing a survey without starting from scratch,Why did you choose to take the question tour? - To see if Qualtrics provides the features I need to build the survey I have planned,"Why did you choose to take the question tour? - None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",The question tour has helped me learn how to configure my own questions in Qualtrics.,How old are you?,Which type of multiple-choice question is easiest to use?,What kind of survey template would be most helpful in building a new survey for your needs?,Please provide your feedback on the content included in the question tour.,Name and contact information - First name,Name and contact information - Last name,Name and contact information - Email address,Name and contact information - Phone number (U.S.),Please rank the following question types in order of usefulness. - Single-answer multiple choice questions,Please rank the following question types in order of usefulness. - Multiple-answer multiple choice questions,Please rank the following question types in order of usefulness. - Single-line text entry questions,Please rank the following question types in order of usefulness. - Essay box text entry questions,Please rank the following question types in order of usefulness. - Rank order questions,Please rate the following in terms of how much you agree or disagree with each statement. - I can build the kind of questions necessary to collect the feedback I need using Qualtrics,Please rate the following in terms of how much you agree or disagree with each statement. - Qualtrics provides a flexible survey tool that can be configured to meet my needs,Please rate the following in terms of how much you agree or disagree with each statement. - I can use a variety of question types to accomplish my research goals,Please rate the question tour according to the following attributes. - Length,Please rate the question tour according to the following attributes. - Level of detail,Please rate the question tour according to the following attributes. - Range of content,How would you rate each of the following question types? - Ease of use - Multiple choice,How would you rate each of the following question types? - Ease of use - Text entry,How would you rate each of the following question types? - Ease of use - Text / Graphic,How would you rate each of the following question types? - Ease of use - Matrix table,How would you rate each of the following question types? - Ease of use - Slider,How would you rate each of the following question types? - Ease of use - Form field,How would you rate each of the following question types? - Ease of use - Rank order,How would you rate each of the following question types? - Ease of use - Side by side,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Multiple choice,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Text entry,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Text / Graphic,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Matrix table,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Slider,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Form field,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Rank order,How would you rate each of the following question types? - Do you plan to use this question type in your survey? - Side by side,End of survey summary -"{""ImportId"":""startDate"",""timeZone"":""Z""}","{""ImportId"":""endDate"",""timeZone"":""Z""}","{""ImportId"":""status""}","{""ImportId"":""ipAddress""}","{""ImportId"":""progress""}","{""ImportId"":""duration""}","{""ImportId"":""finished""}","{""ImportId"":""recordedDate"",""timeZone"":""Z""}","{""ImportId"":""_recordId""}","{""ImportId"":""recipientLastName""}","{""ImportId"":""recipientFirstName""}","{""ImportId"":""recipientEmail""}","{""ImportId"":""externalDataReference""}","{""ImportId"":""locationLatitude""}","{""ImportId"":""locationLongitude""}","{""ImportId"":""distributionChannel""}","{""ImportId"":""userLanguage""}","{""ImportId"":""QID4""}","{""ImportId"":""QID5"",""choiceId"":""1""}","{""ImportId"":""QID5"",""choiceId"":""2""}","{""ImportId"":""QID5"",""choiceId"":""3""}","{""ImportId"":""QID5"",""choiceId"":""4""}","{""ImportId"":""QID5"",""choiceId"":""5""}","{""ImportId"":""QID5"",""choiceId"":""6""}","{""ImportId"":""QID6""}","{""ImportId"":""QID16""}","{""ImportId"":""QID7""}","{""ImportId"":""QID15_TEXT""}","{""ImportId"":""QID8_TEXT""}","{""ImportId"":""QID9_1""}","{""ImportId"":""QID9_2""}","{""ImportId"":""QID9_3""}","{""ImportId"":""QID9_4""}","{""ImportId"":""QID10_1""}","{""ImportId"":""QID10_2""}","{""ImportId"":""QID10_3""}","{""ImportId"":""QID10_4""}","{""ImportId"":""QID10_5""}","{""ImportId"":""QID11_1""}","{""ImportId"":""QID11_2""}","{""ImportId"":""QID11_3""}","{""ImportId"":""QID12_1""}","{""ImportId"":""QID12_2""}","{""ImportId"":""QID12_3""}","{""ImportId"":""QID13#1_1""}","{""ImportId"":""QID13#1_2""}","{""ImportId"":""QID13#1_3""}","{""ImportId"":""QID13#1_4""}","{""ImportId"":""QID13#1_5""}","{""ImportId"":""QID13#1_6""}","{""ImportId"":""QID13#1_7""}","{""ImportId"":""QID13#1_8""}","{""ImportId"":""QID13#2_1""}","{""ImportId"":""QID13#2_2""}","{""ImportId"":""QID13#2_3""}","{""ImportId"":""QID13#2_4""}","{""ImportId"":""QID13#2_5""}","{""ImportId"":""QID13#2_6""}","{""ImportId"":""QID13#2_7""}","{""ImportId"":""QID13#2_8""}","{""ImportId"":""QID18""}" -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,84,True,2024-01-21 01:37:24,R_07F4QiGAgudB3GS,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,55-64 years old,Single-answer multiple choice questions,,,Wayne,Ross,Wayne.Ross@fakeemail.com,521-231-1500,1,2,5,4,3,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Not sure,Yes,Yes,Not sure,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,112,True,2024-01-21 01:37:24,R_0oKgKIRCjSrn5Cm,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,35-44 years old,Single-answer multiple choice questions,healthcare,I thought this would have more information on how to use Qualtrics instead of just showing me questions I can easily build myself or see in any template that is already built. It would be more useful to demonstrate how to use advanced features and spend less time focusing on basics that are easy for anybody to learn by doing.,Robert,Williams,Robert.Williams@fakeemail.com,521-231-2500,2,3,4,5,1,Somewhat agree,Somewhat agree,Strongly agree,4,4,3,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,99,True,2024-01-21 01:37:24,R_2rVJszl5QQXpWaq,,,,,,,preview,EN,Neither useful nor not useful,To learn about different question types and how to use them,To see best practices in survey construction and design,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Neither agree nor disagree,65+ years old,Multiple-answer multiple choice questions,You already have the templates I need!,This is a good starting point but it would be great if there was more content on methodology! I like the different examples and notes about improving the survey-takers' experiences,Judith,Peterson,Judith.Peterson@fakeemail.com,521-231-7700,1,2,3,4,5,Somewhat agree,Neither agree nor disagree,Somewhat agree,3,4,3,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Yes,Yes,Yes,Yes,No,No,Yes,Yes,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,123,True,2024-01-21 01:37:24,R_3HIq5UEnwg8TaES,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,,,Strongly agree,35-44 years old,Single-answer multiple choice questions,,,Eric,Wright,Eric.Wright@fakeemail.com,521-231-5400,2,3,5,4,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Very easy,Neither easy nor difficult,Yes,Yes,Yes,Yes,No,Not sure,Yes,Yes,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,120,True,2024-01-21 01:37:24,R_3I7GkdZfeYy8QOa,,,,,,,preview,EN,Neither useful nor not useful,,,,,,"None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",Neither agree nor disagree,55-64 years old,Single-answer multiple choice questions,,,Hannah,Kelly,Hannah.Kelly@fakeemail.com,521-231-7500,1,2,5,4,3,Somewhat agree,Somewhat disagree,Neither agree nor disagree,3,3,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Yes,Yes,Yes,Yes,No,Yes,Yes,Need more information,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_3l4IJE1mD4Q0EKi,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,45-54 years old,Single-answer multiple choice questions,csat,the content is pretty good but I wish there was more information on complex configurations,Alice,Wood,Alice.Wood@fakeemail.com,521-231-5600,2,3,4,1,5,Strongly agree,Strongly agree,Somewhat agree,5,4,5,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Not sure,Yes,Yes,Yes,No,Yes,Need more information,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,93,True,2024-01-21 01:37:24,R_57KVKagvu2wgMOW,,,,,,,preview,EN,Useful,,To see best practices in survey construction and design,,,,,Somewhat agree,25-34 years old,Select box multiple choice questions,,I would like to learn more about different research types and what questions work best for those populations,Katherine,Hill,Katherine.Hill@fakeemail.com,521-231-7500,1,2,3,4,5,Strongly agree,Somewhat agree,Somewhat agree,4,4,4,Somewhat easy,Neither easy nor difficult,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Somewhat easy,Somewhat easy,Yes,Yes,Yes,Yes,Yes,No,Yes,No,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,119,True,2024-01-21 01:37:24,R_6WirOjrtq9pdD2S,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,18-24 years old,Single-answer multiple choice questions,product feedback,more content on the more advanced and complex question types - there is a lot of really useful information about basic questions (multiple choice) and less detail about complex questions types (side-by-side) and it would be helpful to understand when those would be best used and how to set them up in the tool.,Julie,Stewart,Julie.Stewart@fakeemail.com,521-231-8500,1,2,4,5,3,Strongly agree,Strongly agree,Somewhat agree,4,4,4,Very easy,Very easy,Very easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Very easy,Somewhat difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,79,True,2024-01-21 01:37:24,R_8H3GCq0wtaNCfKm,,,,,,,preview,EN,Not useful,,,,To practice building and editing a survey without starting from scratch,,,Somewhat disagree,45-54 years old,Single-answer multiple choice questions,,,Catherine,Roberts,Catherine.Roberts@fakeemail.com,521-231-5000,1,2,4,5,3,Neither agree nor disagree,Somewhat disagree,Neither agree nor disagree,3,3,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Yes,Not sure,Not sure,Yes,Not sure,Not sure,Yes,No,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,75,True,2024-01-21 01:37:24,R_aiaBjSWugjTnXgO,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,25-34 years old,Single-answer multiple choice questions,"event planning, event feedback",I think this is really helpful and I like the different examples that illustrate how different question types work.,Brian,Gonzalez,Brian.Gonzalez@fakeemail.com,521-231-5500,3,4,5,1,2,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Neither easy nor difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_bf41KXAuaRyVP9A,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Somewhat agree,18-24 years old,Single-answer multiple choice questions,,,Diane,Edwards,Diane.Edwards@fakeemail.com,521-231-6500,1,2,4,3,5,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Yes,Not sure,Yes,No,Not sure,Yes,No,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,139,True,2024-01-21 01:37:24,R_d3Zr8ngGxJ85rF4,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,To see best practices in survey construction and design,,,,,Strongly agree,45-54 years old,Single-answer multiple choice questions,,I would like more information about conditional questions and survey flow,Joyce,Flores,Joyce.Flores@fakeemail.com,521-231-3000,1,2,5,4,3,Somewhat agree,Strongly agree,Strongly agree,4,4,4,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Neither easy nor difficult,Somewhat easy,Somewhat easy,Yes,Not sure,Yes,Yes,Not sure,No,Yes,Need more information,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,120,True,2024-01-21 01:37:24,R_ddwpvVglXabjWm2,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,35-44 years old,Single-answer multiple choice questions,,,Jose,Edwards,Jose.Edwards@fakeemail.com,521-231-8100,3,4,5,2,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat easy,Somewhat difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,No,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,189,True,2024-01-21 01:37:24,R_di2gEC0rP1J0NzE,,,,,,,preview,EN,Neither useful nor not useful,,,,To practice building and editing a survey without starting from scratch,,,Neither agree nor disagree,65+ years old,Dropdown list multiple choice questions,"product feedback, website feedback",this should be more interactive and include other features,Jerry,Torres,Jerry.Torres@fakeemail.com,521-231-5000,2,5,3,1,4,Somewhat agree,Somewhat disagree,Neither agree nor disagree,3,2,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Somewhat difficult,Neither easy nor difficult,Very difficult,Yes,Yes,Yes,Yes,No,Not sure,Yes,Need more information,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,124,True,2024-01-21 01:37:24,R_e3Ot9ljVM4VVObs,,,,,,,preview,EN,Not useful,,,,,,"None of the above (this answer is ""exclusive"" which means you can't choose it with any other option - it can be set using the response menu that becomes available when you click into the answer to make edits)",Somewhat disagree,65+ years old,Dropdown list multiple choice questions,,,Evelyn,Rogers,Evelyn.Rogers@fakeemail.com,521-231-6700,1,2,5,3,4,Neither agree nor disagree,Somewhat disagree,Somewhat disagree,3,2,1,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Neither easy nor difficult,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,Not sure,No -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,116,True,2024-01-21 01:37:24,R_enh6S6dzmkN04zY,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,To practice building and editing a survey without starting from scratch,,,Strongly agree,18-24 years old,Single-answer multiple choice questions,,,Philip,Perry,Philip.Perry@fakeemail.com,521-231-5200,1,2,5,4,3,Strongly agree,Somewhat agree,Strongly agree,5,5,5,Somewhat easy,Somewhat easy,Very easy,Somewhat easy,Somewhat easy,Somewhat difficult,Somewhat easy,Very difficult,Yes,Yes,Yes,Yes,Yes,Yes,Yes,Need more information,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,104,True,2024-01-21 01:37:24,R_1YSV94HnpeUAHpY,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,To see if Qualtrics provides the features I need to build the survey I have planned,,Strongly agree,18-24 years old,Single-answer multiple choice questions,,Really useful to see how different question types work. Especially like the comments on best practices and how to optimize for mobile - would like even more of that type of content.,Christina,Murphy,Christina.Murphy@fakeemail.com,521-231-7900,2,1,3,4,5,Strongly agree,Strongly agree,Strongly agree,5,5,4,Very easy,Very easy,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,Yes,Yes,Yes,No,No,Yes,No,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,89,True,2024-01-21 01:37:24,R_3HJUaSjRfCBgCH4,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,To explore the different features and functionality of the survey editor tool,,,,Strongly agree,35-44 years old,Single-answer multiple choice questions,NPS,I feel like it's pretty long but also has been useful,Billy,Gray,Billy.Gray@fakeemail.com,521-231-9100,2,3,5,4,1,Strongly agree,Strongly agree,Somewhat agree,4,5,5,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Very easy,Somewhat difficult,Yes,Yes,Yes,Yes,Not sure,Yes,Yes,Need more information,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,77,True,2024-01-21 01:37:24,R_3vXXb7tv4uogYM6,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,,,Strongly agree,45-54 years old,Single-answer multiple choice questions,,,Emma,Ramirez,Emma.Ramirez@fakeemail.com,521-231-6500,3,2,5,4,1,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Very easy,Yes,Yes,Yes,Yes,Yes,Not sure,Not sure,Not sure,Yes -2021-09-02 00:29:50,2021-09-02 00:29:50,Imported,,100,630,True,2024-01-21 01:37:24,R_cZPXTgwJn4BL5rw,,,,,,,preview,EN,Useful,To learn about different question types and how to use them,,,,,,Strongly agree,25-34 years old,Single-answer multiple choice questions,polls,I thought there would be more information on the survey builder tool and how to use it,Sara,Brooks,Sara.Brooks@fakeemail.com,521-231-4500,1,2,4,5,3,Strongly agree,Strongly agree,Strongly agree,5,5,5,Very easy,Very easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Very easy,Somewhat easy,Yes,No,Yes,Yes,Not sure,No,Yes,Need more information,Yes -2024-02-02 12:17:53,2024-02-03 08:40:08,IP Address,104.28.124.180,73,73335,False,2024-02-10 08:40:09,R_62LwG6itKcvqz6j,,,,,,,anonymous,EN,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, From d3506baa1b12d89395fc0357b2e08798af756dc1 Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Sun, 29 Sep 2024 17:06:53 -0700 Subject: [PATCH 7/8] Changed generation code to accurately reflect new file --- wearipedia/devices/qualtrics/qualtrics_gen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 3e824a1a..74f53aa0 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -5,14 +5,14 @@ def create_syn_data(): """ Creates synthetic survey data based on a sample survey CSV file. - This function reads a sample survey CSV file located in the same directory as the script - and returns its contents as a Pandas DataFrame. The function assumes that the sample survey - CSV file is named "sample_survey.csv". + This function reads a sample patient intake CSV file located in the same directory as the script + and returns its contents as a Pandas DataFrame. The function assumes that this sample survey + CSV file is named "patient_intake.csv". :return: A DataFrame containing the synthetic survey data from the sample CSV file. :rtype: pd.DataFrame """ script_dir = os.path.dirname(__file__) - survey_file = os.path.join(script_dir, "sample_survey.csv") + survey_file = os.path.join(script_dir, "patient_intake.csv") df = pd.read_csv(survey_file) return df From 8d6b23c020489b3e3a1eea3f4e147a681706aded Mon Sep 17 00:00:00 2001 From: Hee Jung Choi Date: Thu, 17 Oct 2024 15:28:20 -0700 Subject: [PATCH 8/8] fixed synthetic bug --- wearipedia/devices/qualtrics/qualtrics_gen.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wearipedia/devices/qualtrics/qualtrics_gen.py b/wearipedia/devices/qualtrics/qualtrics_gen.py index 74f53aa0..f7a03be7 100644 --- a/wearipedia/devices/qualtrics/qualtrics_gen.py +++ b/wearipedia/devices/qualtrics/qualtrics_gen.py @@ -1,14 +1,16 @@ import os import pandas as pd -def create_syn_data(): +def create_syn_data(synthetic_survey): """ Creates synthetic survey data based on a sample survey CSV file. This function reads a sample patient intake CSV file located in the same directory as the script and returns its contents as a Pandas DataFrame. The function assumes that this sample survey CSV file is named "patient_intake.csv". - + + :param synthetic_survey: Dummy variable that is not used in this function. + :type synthetic_survey: str :return: A DataFrame containing the synthetic survey data from the sample CSV file. :rtype: pd.DataFrame """