Skip to content

Commit 2f6c391

Browse files
author
Suchard
committed
Fix gitignore
1 parent 9d0c0ff commit 2f6c391

2 files changed

Lines changed: 180 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
get_individual_progress.py
1+
venv/

get_individual_progress.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import requests
2+
from qualtrics_settings import BASE_URL, HEADERS, SURVEYIDS, QualtricsAPIError
3+
import time
4+
import zipfile
5+
import io
6+
import pandas as pd
7+
8+
def create_data_export(survey_id: str) -> str:
9+
"""Create a data export request for a given survey.
10+
11+
Args:
12+
survey_id (str): The ID of the survey to export.
13+
Raises:
14+
QualtricsAPIError: If there is an error during the request or unexpected response format.
15+
Returns:
16+
str: The ID of the data export request.
17+
"""
18+
print("Creating data export request... ")
19+
endpoint = f"{BASE_URL}/surveys/{survey_id}/export-responses"
20+
payload = {
21+
"format": "csv"
22+
}
23+
try:
24+
response = requests.post(endpoint, headers=HEADERS, json=payload)
25+
response.raise_for_status()
26+
request_id = response.json()['result']["progressId"]
27+
return request_id
28+
except requests.exceptions.RequestException as exc:
29+
raise QualtricsAPIError(
30+
f"Error while creating data export for survey {survey_id}."
31+
) from exc
32+
except (KeyError, TypeError, ValueError) as exc:
33+
raise QualtricsAPIError(
34+
f"Unexpected response format when creating data export for survey {survey_id}."
35+
) from exc
36+
37+
38+
def check_export_progress(request_id: str, survey_id: str, returns: str) -> str|float:
39+
"""Check if a an export request is complete
40+
41+
Args:
42+
request_id (str): The ID of the export request.
43+
survey_id (str): The ID of the survey that was exported.
44+
returns (str): The type of information to return ("percent_complete" or "fileID").
45+
Raises:
46+
QualtricsAPIError: If there is an error during the request or unexpected response format.
47+
48+
Returns:
49+
str: The percent complete if the request is still processing, or the file ID if complete.
50+
"""
51+
print("Checking on export progress... ")
52+
endpoint = f"{BASE_URL}/surveys/{survey_id}/export-responses/{request_id}"
53+
try:
54+
result = requests.get(endpoint, headers=HEADERS)
55+
result.raise_for_status()
56+
57+
if returns == "percent_complete":
58+
percent_complete = result.json()["result"]["percentComplete"]
59+
return percent_complete
60+
elif returns == "fileID":
61+
file_id = percent_complete = result.json()["result"]["fileId"]
62+
return file_id
63+
except requests.exceptions.RequestException as exc:
64+
raise QualtricsAPIError(
65+
f"Error while checking the progress {survey_id}."
66+
) from exc
67+
except (KeyError, TypeError, ValueError) as exc:
68+
raise QualtricsAPIError(
69+
f"Unexpected response format when checking the progress for survey {survey_id}."
70+
) from exc
71+
72+
73+
def download_export(file_id: str, survey_id: str) -> pd.DataFrame:
74+
"""Download the exported survey data and convert to DataFrame.
75+
76+
Args:
77+
file_id (str): The ID of the file to download.
78+
survey_id (str): The ID of the survey associated with the file.
79+
Raises:
80+
QualtricsAPIError: If there is an error during the request or unexpected response format.
81+
Returns:
82+
pd.DataFrame: The survey response data as a DataFrame.
83+
"""
84+
print("Downloading export file... ")
85+
endpoint = f"{BASE_URL}/surveys/{survey_id}/export-responses/{file_id}/file"
86+
try:
87+
download = requests.get(
88+
endpoint,
89+
headers=HEADERS,
90+
stream=True
91+
)
92+
download.raise_for_status()
93+
94+
with zipfile.ZipFile(io.BytesIO(download.content)) as z:
95+
csv_name = [name for name in z.namelist() if name.endswith(".csv")][0]
96+
with z.open(csv_name) as f:
97+
df = pd.read_csv(f)
98+
return df
99+
except requests.exceptions.RequestException as exc:
100+
raise QualtricsAPIError(
101+
f"Error while checking the progress {survey_id}."
102+
) from exc
103+
except (KeyError, TypeError, ValueError) as exc:
104+
raise QualtricsAPIError(
105+
f"Unexpected response format when checking the progress for survey {survey_id}."
106+
) from exc
107+
108+
109+
def check_inputs_validity(participant_study_id: str, embedded_data_field: str, survey_id: str) -> bool:
110+
"""Checks the validaity of arguments.
111+
112+
Args:
113+
participant_study_id (str): ID of the participant whose progress you want to check.
114+
embedded_data_field (str): The field name in Qualtrics where the participant_study_id is stored.
115+
survey_id (str): The ID of the survey to check progress in.
116+
117+
Raises:
118+
ValueError: If any argument is invalid.
119+
120+
Returns:
121+
bool: True if all inputs are valid.
122+
"""
123+
print("Checking input validity... ")
124+
if not isinstance(survey_id, str) or not survey_id.startswith("SV_"):
125+
raise ValueError("Invalid survey_id. It should be a string starting with 'SV_'.")
126+
127+
if not isinstance(embedded_data_field, str) or embedded_data_field.strip() == "":
128+
raise ValueError("Invalid embedded_data_field. It should be a non-empty string.")
129+
130+
if not isinstance(participant_study_id, str) or participant_study_id.strip() == "":
131+
raise ValueError("Invalid participant_study_id. It should be a non-empty string.")
132+
return True
133+
134+
def get_individual_progress(participant_study_id: str, embedded_data_field: str, survey_id: str) -> float:
135+
"""Wrapper function that gets the individual progress of a participant in a survey.
136+
137+
Args:
138+
participant_study_id (str): ID of the participant whose progress you want to check.
139+
embedded_data_field (str): The field name in Qualtrics where the participant_study_id is stored.
140+
survey_id (str): The ID of the survey to check progress in.
141+
Raises:
142+
QualtricsAPIError: If the participant is not found in this survey.
143+
Returns:
144+
float: Progress of the participant in the survey (0-100).
145+
"""
146+
# Check for invalid inputs
147+
check_inputs_validity(participant_study_id, embedded_data_field, survey_id)
148+
149+
# Request a data export
150+
request_id = create_data_export(survey_id)
151+
152+
# Ping the export report repeatedly until it's ready
153+
export_progress = 0.0
154+
while export_progress < 100:
155+
export_progress = check_export_progress(request_id, survey_id, returns="percent_complete")
156+
time.sleep(1)
157+
158+
# When export is complete, get the file ID
159+
file_id = check_export_progress(request_id, survey_id, returns="fileID")
160+
161+
if file_id:
162+
df = download_export(file_id, survey_id)
163+
print(df)
164+
individual_progress = df[df[embedded_data_field] == participant_study_id]
165+
166+
if not individual_progress.empty:
167+
individual_progress = individual_progress["Progress"].values[0]
168+
return individual_progress
169+
else:
170+
print(f"No data found for participant_study_id: {participant_study_id} in this survey.")
171+
raise QualtricsAPIError("Participant not found in survey data.")
172+
173+
if __name__ == "__main__":
174+
participant_study_id="11111TEST11111"
175+
survey_id=SURVEYIDS.my_test_survey2_id
176+
embedded_data_field = "study_id_child"
177+
178+
result = get_individual_progress(participant_study_id, embedded_data_field, survey_id)
179+
print(result)

0 commit comments

Comments
 (0)