콘텐츠로 이동
✍️ 수정가능누구나 고쳐도 됩니다. 고치면 하단 frontmatter의 갱신일·작성자·변경요약을 남겨 주세요.작성 Claude · 2026-06-05 · open_challenge 실제 코드(단일 객관식·AI코칭·API)로 엔티티·상태·권한 전면 정렬

챌린지 도메인 (Challenge)

1. 한 줄 정의

학생이 객관식 문항을 풀고, 막히면 AI 코칭으로 사고를 유도받는 챌린지 도메인. 1차 MVP 핵심 채널 (오픈챌린지 v2/v3). 비로그인 학생은 공개 챌린지(status=PUBLIC)로 유입되고, 막힘 시 AI 코칭 세션(ai_coaching_*)이 붙는다. 코드상 챌린지는 패키지 open_challenge에 단일 도메인으로 존재하며, "스터디룸 전용 챌린지(visibility=STUDYROOM)"는 코드에 없다(노출 제어는 ChallengeStatus PUBLIC/HIDDEN). 기능 명세는 챌린지식 풀이 · 오픈챌린지 참조.

2. 핵심 개념 (실제 코드 — 패키지 open_challenge)

코드의 챌린지는 "여러 문제 묶음"이 아니라 단일 객관식 문항 모델이다. 한 Challenge가 하나의 문제(지문+보기+정답)이고, 시도는 답 1개를 고른다. 아래 wiki 옛 서술의 ChallengeProblem·ChallengeRubric·ChallengeProblemAttempt·ChallengeScore·qnaContextId·visibility(PUBLIC/STUDYROOM)코드에 없다.

  • Challenge (challenge) — 단일 객관식 문항. subject(ChallengeSubject=MATH), difficulty(ChallengeDifficulty=TOP/HIGH/MID/LOW), wrongAnswerRate, title, sourceText, questionText, questionImageUrl/questionMediaId, choices(JSON 리스트), correctAnswer, type, isAiSupported, status(ChallengeStatus=PUBLIC/HIDDEN), participantCount/correctCount. soft delete는 deleted_at 컬럼만(엔티티에 @SQLDelete 미선언 — from()에서 수동 set)
  • ChallengeAttempt (challenge_attempt) — 사용자 1회 시도. challengeId, userId, status(ChallengeAttemptStatus=IN_PROGRESS/AI_COACHING/UNRESOLVED/COMPLETED), selectedAnswer, isCorrect, usedAi, maxUsedHintStep, startedAt/completedAt
  • AiCoachingSession / AiCoachingMessage / AiCoachingPreference (ai_coaching_*) — AI 코칭 서브시스템. 세션 status(AiCoachingSessionStatus=READY/COACHING/WAITING_ANSWER/GUIDE_TO_PROBLEM/FINISHED/ABANDONED), 메시지 role(AiCoachingMessageRole), 학습 선호(LearningGoal·LearningStage·DifficultArea·AiProvider)
  • ChallengeFeedback — 챌린지 피드백, ChallengeReview/ChallengeReviewRecommend — 풀이 후기·추천, UserRanking — 랭킹 집계

3. 관련 코드

구현 명세는 챌린지식 풀이 (스터디룸 챌린지) + 오픈챌린지 (비로그인 공개)에서 관리. 패키지 배치:

mvp-back/src/main/java/com/example/demo/
├─ domain/open_challenge/{challenge,challengeattempt,aicoaching,challengefeedback,challengereview,userranking}/
├─ application/service/open_challenge/...
├─ presentation/{controller,dto}/open_challenge/...
└─ infrastructure/persistence/{entity,repository}/open_challenge/...

mvp-front route group: - (public)/challenge — 비로그인 공개 리스트 - (private)/challenge — 스터디룸 챌린지 + 진행

주요 API (실제 구현 — open_challenge 컨트롤러들)

Method Path 설명
GET /api/public/challenges[/{challengeId}] 비로그인 리스트·상세
GET /api/public/challenge-rankings 공개 랭킹
POST /api/common/challenge-attempts 시도 시작
PATCH /api/common/challenge-attempts/{attemptId}/{answer,abandon} 답 제출·포기
GET /api/common/challenges/{challengeId}/my-active-attempt 진행 중 시도
POST/GET/PATCH /api/common/ai-coaching-sessions[/{sessionId}/{messages,finish,abandon}] AI 코칭 세션·메시지
GET/PUT /api/common/ai-coaching-preferences/{enums,me} 코칭 선호
GET /api/common/me/challenges[/{challengeId}] 내 챌린지 기록
POST /api/common/challenge-feedbacks 피드백
POST/DELETE/GET /api/common/challenge-reviews[/{reviewId}/recommends], /api/common/challenges/{id}/reviews 후기·추천
GET /api/teacher/students/{studentId}/challenges/{challengeId}/ai-log 선생 AI 로그
PUT/PATCH/DELETE /api/admin/challenges/{challengeId}[/{hide,show}] 운영자 챌린지 관리(출제·숨김)

로그인 사용자 진입은 대부분 /api/common/**(TEACHER·STUDENT·PARENT) prefix이고, /api/student/...attempts/.../problems/... 같은 multi-problem 경로는 존재하지 않는다. 챌린지 출제·노출 제어는 /api/admin/challenges/**.

4. 상태/생명주기

ChallengeAttempt.status (ChallengeAttemptStatus):
  IN_PROGRESS → COMPLETED          (답 제출, isCorrect 판정)
              → AI_COACHING        (막힘 시 AI 코칭 진입)
              → UNRESOLVED         (미해결 종료)

AiCoachingSession.status:
  READY → COACHING → WAITING_ANSWER → GUIDE_TO_PROBLEM → FINISHED
                                                       → ABANDONED

채점은 항상 서버 단일 진실원천. correctAnswer는 챌린지 엔티티에만 두고, 시도(selectedAnswer) 정오는 서버가 isCorrect로 판정한다.

5. 외부 의존

  • LLM provider — AI 코칭 세션. 공통 external/ai(AiCoachingClient + Anthropic/OpenAI/Stub)를 통해 호출. (객관식 채점은 correctAnswer 직접 비교라 LLM 불필요. 옛 서술의 ChallengeRubricScoringService·rubric 채점은 코드에 없음)
  • S3 / MediaAsset — 문제 이미지(questionMediaId/questionImageUrl)
  • Spring Security — /api/public/challenges/**·/api/public/challenge-rankings permitAll → member §7 비회원 정합

6. UI 노출

  • /challenge 공개 리스트 (비로그인)
  • /challenge/[id] 챌린지 상세 안내
  • /challenge/[id]/attempts/[attemptId] 풀이 진행 (단계 인디케이터 + 사이드 패널)
  • 결과 화면 — 사용한 힌트 단계까지 노출해 사고력 가시화
  • AI 코치 화면 흐름은 단계 인디케이터·사이드 패널 기반 진행 UI를 따른다

7. 권한 / 정책 요약

역할 권한
비회원 status=PUBLIC 챌린지 리스트·상세·랭킹 (/api/public/challenges/**·challenge-rankings permitAll)
로그인 사용자(공통) 시도 시작·답 제출(answer)·포기(abandon)·AI 코칭·후기 — /api/common/**(TEACHER·STUDENT·PARENT)
선생님 소속 학생 AI 로그 모니터링 (/api/teacher/students/{id}/challenges/{id}/ai-log)
운영자(ADMIN) 챌린지 출제·수정·숨김/노출·삭제 (/api/admin/challenges/**)
  • 채점·정답은 서버 단일 진실원천 (§5), correctAnswer는 클라이언트에 미노출
  • 챌린지 노출 제어는 ChallengeStatus(PUBLIC/HIDDEN)이며, 별도 visibility(PUBLIC/STUDYROOM) 컬럼은 코드에 없음
  • 역할별 접근 권한 SSOT는 member §7

8. 결정 이력 / TODO

  • 챌린지식 풀이 채택 (2026-05-20) → 챌린지식 풀이
  • 오픈챌린지 백/프론트 MVP 완성 (2026-05-26) → 오픈챌린지
  • (예정) ADR-0006 — 챌린지 vs homework 경계 (출제 주체·범위 다름으로 별개 유지)
  • (예정) ADR-0007 — 비로그인 공개 엔드포인트 Spring Security 정책
  • 챌린지가 studyroom 안인지 글로벌인지 → 코드는 글로벌 단일 도메인(ChallengeStatus로 노출만 제어). 스터디룸 귀속은 미구현
  • qna와의 관계 → 막힘 코칭은 QnA가 아니라 챌린지 자체의 ai_coaching_* 서브시스템으로 구현됨(qnaContextId FK는 코드에 없음)
  • 챌린지 출제 — 현재 /api/admin/challenges/**(운영자)로 출제·노출 제어. 선생님 직접 출제는 미구현
  • 6모 시즌 트래픽 spike — 1차 MVP는 모니터링 알림만, 캐시·스케일은 2차