본문으로 건너뛰기
버전: 0.68.0

설문 응답 제출 API

설문 응답 제출 API는 사용자가 특정 설문의 모든 질문에 대한 답변을 완료한 후, 해당 설문의 응답을 제출하는 기능을 제공합니다. 제출된 응답은 검증을 거쳐 저장되며, 점수 시스템이 있는 설문의 경우 자동으로 점수가 계산됩니다.

설문 개별 응답 제출

사용자가 작성한 설문 응답을 제출하고, 점수 계산 결과를 반환합니다.

  • HTTP Method: POST
  • Path: /questionnaires/:questionnaireId/rounds/:roundId/responses
  • 인증: Access Token 필요 (Authorization: Bearer {accessToken})

Headers

HeaderTypeDescriptionRequired
Content-Typeapplication/json요청 본문 형식을 지정합니다.Yes
AuthorizationstringBearer 토큰 형식의 JWT 액세스 토큰이 필요합니다.Yes
Accept-Languagestring언어 설정을 지정합니다. (예: ko, en)Yes

Path Parameters

ParameterTypeDescriptionExample
questionnaireIdstring응답을 제출할 설문의 ID19bc1965-3b9c-4132-adba-f1f42a95bb98
roundIdstring응답을 제출할 설문 회차의 ID8f777de6-fe64-4074-9e7e-c5ee77f10e6b

Request Body (SubmitQuestionnaireResponseRequestDto)

설문 응답 데이터를 포함합니다. 각 질문 타입에 따라 적절한 형식으로 답변을 제출해야 합니다.

{
"questionResponses": [
{
"id": "2fae3e99-71d8-44d8-94b0-0f4e88b4e5ef",
"type": "SINGLE_CHOICE",
"responses": [
{
"value": "0",
"optionId": "a5757dd4-361b-42f1-9ba9-0c062fa3d410",
"userInputText": "my_drug_name"
}
],
"decisionTimesMs": [7377]
},
{
"id": "b36119b6-d2e1-4bb8-ad58-9af110379daf",
"type": "SINGLE_CHOICE",
"responses": [
{
"value": "2",
"optionId": "3d4eec5c-5424-407e-9b56-4d0435fa567a"
}
],
"decisionTimesMs": [4438]
},
{
"id": "b9222365-0d7f-4992-9c5f-5a245af8ac75",
"type": "SINGLE_CHOICE",
"responses": [
{
"value": "4",
"optionId": "94ce8547-6dd9-403d-bc7f-de6553f3e85c"
}
],
"decisionTimesMs": [5476]
}
]
}
필드타입설명필수 (Yes/No)
questionResponsesarray질문별 응답 목록 (QuestionResponseDto[])Yes
questionResponses[].idstring질문 IDYes
questionResponses[].typestring질문 타입 (QuestionType 참조)Yes
questionResponses[].responsesarray응답 선택지 목록 (QuestionResponseOptionDto[])Yes
questionResponses[].responses[].optionIdstring선택지 ID (옵션 기반 질문의 경우 필수)No
questionResponses[].responses[].valuestring답변 값Yes
questionResponses[].responses[].userInputTextstring사용자 텍스트 입력 값 (선택사항, 약물명 등)No
questionResponses[].textValuestring텍스트 응답 (선택사항)No
questionResponses[].decisionTimesMsarray답변 완료까지 총 소요 시간 배열 (밀리초) - 숫자 배열 형태Yes

지원하는 질문 타입 (QuestionType 참조)

질문 타입설명optionId 필요 여부value 형식
MULTI_CHOICE복수 선택형Yes선택한 옵션 값
SINGLE_CHOICE단일 선택형Yes선택한 옵션 값
LINEAR_SCALE선형 척도No숫자 값
TIME시간 입력No시간 값
DURATION_MINUTES소요 시간(분)No분 단위 값
TEXT텍스트 입력No텍스트 값

Responses

HTTP Status Code설명Error Code(s)
200 OK응답 제출 성공-
400 Bad Request잘못된 요청 (검증 오류)9040, 9043, 9041, 9042, 9045, 9044
401 Unauthorized인증 실패2051
404 Not Found리소스 없음 (설문지, 회차, 사용자 주기, 사용자 정보)9030, 9031, 7003, 7008, 9010
409 Conflict중복 응답 제출9046
410 Gone라운드 기간 만료9055
412 Precondition Failed라운드 진행 조건 미충족9054
500 Internal Server Error서버 내부 오류9000, 9500
200 OK - 응답 제출 성공

성공적으로 설문 응답을 제출하면 저장된 응답 정보와 함께 점수 계산 결과가 반환됩니다.

{
"id": "resp_new_123",
"userId": "user_sample_123",
"questionnaireId": "9a0751c8-4c4c-41c2-ba0f-88018f26b014",
"roundId": "2077951e-b4d7-4b59-92d7-0d4bfb482d76",
"score": {
"calculationType": "SUM",
"userScore": 18,
"range": {
"min": {
"value": 0
},
"max": {
"value": 28
}
},
"level": {
"id": "sl_isi3",
"range": {
"min": {
"value": 15
},
"max": {
"value": 21
}
},
"label": "중간 정도의 불면증",
"chartLabel": "15-21 점",
"description": "중등도의 불면 증상이 나타날 수 있습니다.",
"feedback": "중등도의 불면 증상이 나타날 수 있습니다. 의료 전문가의 도움이 필요한 상황입니다."
}
},
"createdAt": 1716800000000,
"completedDayIndex": 1
}
필드타입설명예시필수 (Yes/No)
idstring새로 생성된 QuestionnaireResponse의 IDresp_new_123Yes
userIdstring사용자 IDuser_sample_123Yes
questionnaireIdstring설문 ID9a0751c8-4c4c-41c2-ba0f-88018f26b014Yes
roundIdstring회차 ID2077951e-b4d7-4b59-92d7-0d4bfb482d76Yes
scoreobject점수 정보 (ResponseScoreSectionDto) - 점수 시스템이 있는 설문만No
score.calculationTypestring점수 계산 방식SUMYes (score가 있는 경우)
score.userScorefloat사용자 점수18Yes (score가 있는 경우)
score.rangeobject점수 범위 (ScoreRangeDto)Yes (score가 있는 경우)
score.range.minobject최소 점수 (ScoreRangeValueDto)Yes
score.range.min.valuefloat최소 점수 값0Yes
score.range.maxobject최대 점수 (ScoreRangeValueDto)Yes
score.range.max.valuefloat최대 점수 값28Yes
score.levelobject점수 레벨 정보 (ResponseScoreLevelDto)No (score가 있는 경우)
score.level.idstring점수 레벨 IDsl_isi3Yes
score.level.rangeobject점수 레벨 범위 (ScoreRangeDto)Yes
score.level.range.min.valuefloat점수 레벨 최소값15Yes
score.level.range.max.valuefloat점수 레벨 최대값21Yes
score.level.labelstring점수 레벨 라벨중간 정도의 불면증Yes
score.level.chartLabelstring차트 라벨15-21 점Yes
score.level.descriptionstring설명중등도의 불면 증상이...Yes
score.level.feedbackstring피드백 메시지중등도의 불면 증상이...Yes
createdAtinteger생성 시각 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1716800000000Yes
completedDayIndexinteger완료된 날짜 인덱스1Yes

참고: WIS(Wellness Insight Survey) 등 점수 시스템이 없는 설문의 경우 score 필드가 null로 반환됩니다.

400 Bad Request - 잘못된 요청

예시: 필수 질문 미응답 (9040)

{
"code": 9040,
"message": "REQUIRED_QUESTION_NOT_ANSWERED",
"detail": "필수 질문에 응답하지 않았습니다",
"metadata": {
"questionId": "question_abc_001"
}
}

예시: 필수 질문 빈 값 (9043)

{
"code": 9043,
"message": "REQUIRED_QUESTION_EMPTY_VALUE",
"detail": "필수 질문에 빈 값을 입력할 수 없습니다",
"metadata": {
"questionId": "question_abc_002"
}
}

예시: 질문 타입 불일치 (9041)

{
"code": 9041,
"message": "QUESTION_TYPE_MISMATCH",
"detail": "질문 타입이 일치하지 않습니다",
"metadata": {
"questionId": "question_abc_003",
"expectedType": "SINGLE_CHOICE",
"receivedType": "MULTI_CHOICE"
}
}

예시: 단일 선택 질문에 다중 답변 (9042)

{
"code": 9042,
"message": "SINGLE_CHOICE_MULTIPLE_ANSWERS",
"detail": "단일 선택 질문에는 하나의 답변만 가능합니다",
"metadata": {
"questionId": "question_abc_004",
"answersCount": 2
}
}

예시: 옵션 값 불일치 (9045)

{
"code": 9045,
"message": "OPTION_VALUE_MISMATCH",
"detail": "선택된 옵션의 값이 일치하지 않습니다",
"metadata": {
"questionId": "question_abc_005",
"optionId": "option_123",
"expectedValue": "1",
"receivedValue": "2"
}
}

예시: 지원하지 않는 질문 타입 (9044)

{
"code": 9044,
"message": "UNSUPPORTED_QUESTION_TYPE",
"detail": "지원하지 않는 질문 타입입니다",
"metadata": {
"questionType": "UNKNOWN_TYPE"
}
}
401 Unauthorized - 인증 실패
{
"code": 2051,
"message": "INVALID_TOKEN",
"detail": "토큰이 유효하지 않습니다"
}
404 Not Found - 리소스 없음

예시: 설문지를 찾을 수 없음 (9030)

{
"code": 9030,
"message": "QUESTIONNAIRE_NOT_FOUND",
"detail": "설문지를 찾을 수 없습니다",
"metadata": {
"questionnaireId": "invalid-questionnaire-id"
}
}

예시: 설문 라운드를 찾을 수 없음 (9031)

{
"code": 9031,
"message": "QUESTIONNAIRE_ROUND_NOT_FOUND",
"detail": "설문지 회차를 찾을 수 없습니다",
"metadata": {
"roundId": "invalid-round-id"
}
}

예시: 사용자 활성 주기를 찾을 수 없음 (7003)

{
"code": 7003,
"message": "USER_CYCLE_NOT_FOUND",
"detail": "사용자의 활성 주기를 찾을 수 없습니다",
"metadata": {
"userId": "user_sample_123"
}
}

예시: 사용자 정보를 찾을 수 없음 (7008)

{
"code": 7008,
"message": "USER_STATE_NOT_FOUND",
"detail": "사용자의 정보를 찾을 수 없습니다",
"metadata": {
"userId": "user_sample_123"
}
}

예시: 설문 번들을 찾을 수 없음 (9010)

{
"code": 9010,
"message": "QUESTIONNAIRE_BUNDLE_NOT_FOUND",
"detail": "설문지 번들을 찾을 수 없습니다",
"metadata": {
"userId": "user_sample_123"
}
}
409 Conflict - 중복 응답 제출
{
"code": 9046,
"message": "QUESTIONNAIRE_RESPONSE_ALREADY_EXISTS",
"detail": "해당 설문에 대한 응답이 이미 존재합니다"
}
410 Gone - 라운드 기간 만료

예시: 라운드 진행 기간 만료 (9055)

{
"code": 9055,
"message": "ROUND_EXPIRED",
"detail": "해당 회차 진행 기간이 만료되었습니다"
}
412 Precondition Failed - 라운드 진행 조건 미충족

예시: 라운드 진행 기간 미시작 (9054)

{
"code": 9054,
"message": "ROUND_NOT_STARTED",
"detail": "아직 해당 회차 진행 기간이 시작되지 않았습니다"
}
500 Internal Server Error - 서버 내부 오류

일반적인 서버 오류 (9000)

{
"code": 9000,
"message": "SERVER_ERROR",
"detail": "서버 내부 오류"
}

데이터베이스 접근 오류 (9500)

{
"code": 9500,
"message": "REPOSITORY_ERROR",
"detail": "데이터베이스 접근 중 오류가 발생했습니다"
}

이 오류들은 처리 중 예기치 않은 서버 내부 문제나 데이터베이스 접근 문제가 발생했을 때 반환됩니다.

설명

  • 이 API는 apps/dta-wide-api/src/app/questionnaire/services/questionnaire-response.service.tssubmitQuestionnaireResponse 메서드의 로직을 기반으로 합니다.
  • 주요 검증 단계 (서비스 로직 내):
    1. 사용자의 활성 주기 ID 조회 (getUserCycleId)
    2. 설문지 및 회차 유효성 검증 (CQRS Query를 통해)
    3. 필수 질문 응답 여부 확인
    4. 질문 타입과 답변 형식 일치 여부 검증
    5. 선택지 기반 질문의 경우 optionId와 value 일치 여부 확인
  • JWT 토큰에서 사용자 ID가 자동으로 추출되어 사용됩니다 (req.user). Request Body에서 userId 필드는 더 이상 필요하지 않습니다.
  • 점수 시스템이 있는 설문 (예: ISI, PHQ-9)의 경우 자동으로 점수가 계산되고 적절한 점수 레벨이 할당됩니다.
  • 점수 시스템이 없는 설문 (예: WIS)의 경우 score 필드가 null로 반환됩니다.
  • 제출된 응답은 QuestionnaireResponse 엔티티로 저장되며, 각 답변은 QuestionAnswer 엔티티로 저장됩니다.

QuestionResponseDto 상세

질문별 응답 정보를 담는 DTO에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
idstring질문 IDquestion_abc_001Yes
typestring질문 타입 (QuestionType enum)SINGLE_CHOICEYes
responsesQuestionResponseOptionDto[]응답 선택지 목록(아래 상세 참조)Yes
textValuestring텍스트 응답 (선택사항)추가 의견입니다No
decisionTimesMsarray답변 완료까지 총 소요 시간 배열 (밀리초)[5000]No

QuestionResponseOptionDto 상세

각 응답 선택지에 대한 상세 설명입니다.

필드타입설명예시필수 (Yes/No)
optionIdstring선택지 ID (옵션 기반 질문의 경우 필수)q_opt_001aNo
valuestring답변 값0Yes
userInputTextstring사용자 텍스트 입력 값 (선택사항, 약물명, 기타 의견 등)my_drug_nameNo

ResponseScoreLevelDto 상세

점수에 따른 레벨 정보를 담는 DTO입니다. 점수 시스템이 있는 설문에만 포함됩니다.

필드타입설명예시필수 (Yes/No)
idstring점수 레벨 IDsl_isi3Yes
rangeobject점수 범위Yes
range.minobject최소 점수 객체Yes
range.min.valuefloat최소 점수 값15Yes
range.maxobject최대 점수 객체Yes
range.max.valuefloat최대 점수 값21Yes
labelstring점수 레벨 라벨중간 정도의 불면증Yes
chartLabelstring차트 라벨15-21 점Yes
descriptionstring설명중등도의 불면 증상이 나타날 수 있습니다.Yes
feedbackstring피드백 메시지중등도의 불면 증상이 나타날 수 있습니다. 의료 전문가의 도움이 필요한 상황입니다.Yes

SubmitQuestionnaireResponseResponseDto 상세

설문 응답 제출 성공 시 반환되는 최상위 응답 DTO입니다.

필드타입설명예시필수 (Yes/No)
idstring새로 생성된 QuestionnaireResponse의 IDresp_new_123Yes
userIdstring사용자 IDuser_sample_123Yes
questionnaireIdstring설문 ID9a0751c8-4c4c-41c2-ba0f-88018f26b014Yes
roundIdstring회차 ID2077951e-b4d7-4b59-92d7-0d4bfb482d76Yes
scoreResponseScoreSectionDto점수 정보 (점수 시스템이 있는 설문만)(ResponseScoreSectionDto 참조)No
createdAtinteger생성 시각 (Unix timestamp in milliseconds, Kotlin: Long, Swift: Int64)1716800000000Yes
completedDayIndexinteger완료된 날짜 인덱스1Yes

변경 이력

버전날짜작성자변경 내용
0.1.02025-06-12elizabeth@weltcorp.com최초 문서 작성
0.2.12025-06-20elizabeth@weltcorp.comDTO 구조 수정: score 객체를 별도 필드로 분리, 실제 구현과 문서 일치
0.2.22025-06-20elizabeth@weltcorp.comUserStateNotFoundError (7008) 에러 처리 추가
0.2.32025-06-20elizabeth@weltcorp.comResponseScoreLevelDto에 minScore, maxScore 필드 추가 (bundle DTO와 구조 일치)
0.2.42025-06-20elizabeth@weltcorp.com필드명 변경: questionId→id, questionType→type, optionId null 제거, minScore/maxScore optional 처리
0.2.52025-06-20elizabeth@weltcorp.com필드명 및 DTO명 변경: questionAnswers→questionResponses, QuestionAnswerDto→QuestionResponseDto
0.2.62025-06-21elizabeth@weltcorp.com점수 구조 개편: ResponseScoreDto 도입, decisionTimeMs 배열 타입으로 변경, 새로운 점수 레벨 구조 적용
0.2.72025-06-22elizabeth@weltcorp.comDTO 변경사항 반영: userInputText 필드 추가, decisionTimeMs 배열 타입 명시, userInputText 예시 추가
0.2.82025-06-23elizabeth@weltcorp.comJWT 토큰 기반 인증으로 변경: userId 필드 제거, decisionTimeMs → decisionTimesMs 필드명 변경
0.2.92025-06-23elizabeth@weltcorp.com라운드 진행 기간 검증 에러 추가: RoundNotStartedError (9054), RoundExpiredError (9055) 에러 처리 추가
0.3.02025-07-01elizabeth@weltcorp.com누락된 에러 코드 추가: QUESTIONNAIRE_BUNDLE_NOT_FOUND (9010), QUESTIONNAIRE_RESPONSE_ALREADY_EXISTS (9046) 에러 처리 추가, 중복 응답 제출 시 409 Conflict 상태 코드 추가