diff --git a/backend/app/crud/studies.py b/backend/app/crud/studies.py index 561fa1de683eb84386ac835ff7defc7ab117f004..5848676a762371bbc15532eaa4dfc1ceb4703b19 100644 --- a/backend/app/crud/studies.py +++ b/backend/app/crud/studies.py @@ -164,3 +164,81 @@ def download_study(db: Session, study_id: int): media_type="text/csv", headers={"Content-Disposition": f"attachment; filename={study_id}-surveys.csv"}, ) + + +def download_study_wide(db: Session, study_id: int): + output = StringIO() + writer = csv.writer(output) + + data = {} + question_ids = set() + + db_entries = ( + db.query(models.TestEntry).filter(models.TestEntry.study_id == study_id).all() + ) + + for entry in db_entries: + if entry.entry_task is None: + continue + + user_id = entry.user_id + code = entry.code + item_id = entry.entry_task.test_question_id + key = (user_id, code) + + if key not in data: + if user_id is not None: + user = crud.get_user(db, user_id) + data[key] = { + "study_id": study_id, + "user_id": user_id, + "code": code, + "home_language": user.home_language, + "target_language": user.target_language, + "gender": user.gender, + "birthdate": user.birthdate, + } + else: + data[key] = {"study_id": study_id, "user_id": user_id, "code": code} + + if entry.entry_task.entry_task_qcm: + selected_id = entry.entry_task.entry_task_qcm.selected_id + correct_id = entry.entry_task.test_question.question_qcm.correct + correct_answer = int(selected_id == correct_id) + data[key][item_id] = correct_answer + question_ids.add(item_id) + + if entry.entry_task.entry_task_gapfill: + answer = entry.entry_task.entry_task_gapfill.text + correct = extract_text_between_angle_bracket( + entry.entry_task.test_question.question + ) + correct_answer = int(answer == correct) + data[key][item_id] = correct_answer + question_ids.add(item_id) + + # Sort question IDs for consistent column order + question_ids = sorted(question_ids) + header = [ + "study_id", + "user_id", + "code", + "home_language", + "target_language", + "gender", + "birthdate", + ] + question_ids + writer.writerow(header) + for (user_id, code), values in data.items(): + row = [values.get(col, "") for col in header] + writer.writerow(row) + + output.seek(0) + + return StreamingResponse( + output, + media_type="text/csv", + headers={ + "Content-Disposition": f"attachment; filename={study_id}-surveys-wide.csv" + }, + ) diff --git a/backend/app/routes/studies.py b/backend/app/routes/studies.py index 0cba51e4d8b1a6219c68d9d9d02a18d74846349a..04411b25ef7a1a6850a6fb65d0efc85379a62fac 100644 --- a/backend/app/routes/studies.py +++ b/backend/app/routes/studies.py @@ -71,3 +71,15 @@ def download_study( if study is None: raise HTTPException(status_code=404, detail="Study not found") return crud.download_study(db, study_id) + + +@require_admin("You do not have permission to download this study.") +@studiesRouter.get("/{study_id}/download/surveys-wide") +def download_study( + study_id: int, + db: Session = Depends(get_db), +): + study = crud.get_study(db, study_id) + if study is None: + raise HTTPException(status_code=404, detail="Study not found") + return crud.download_study_wide(db, study_id) diff --git a/frontend/src/routes/admin/studies/+page.svelte b/frontend/src/routes/admin/studies/+page.svelte index 5ca59fd342866aab97f9da7936f1a9d4ee9d74a0..254ee148ed71f390f69d5e95839a5fc87ae1454c 100644 --- a/frontend/src/routes/admin/studies/+page.svelte +++ b/frontend/src/routes/admin/studies/+page.svelte @@ -38,10 +38,16 @@ title="Download" href={`${config.API_URL}/v1/studies/${study.id}/download/surveys`} > - <Icon src={ArrowDownTray} size="16" /> + <Icon src={ArrowDownTray} size="16" /> CSV long + </a> + <a + class="btn btn-primary btn-sm" + title="Download" + href={`${config.API_URL}/v1/studies/${study.id}/download/surveys-wide`} + > + <Icon src={ArrowDownTray} size="16" /> CSV wide </a></td > - <td></td> </tr> {/each} </tbody>