Skip to content

Conversation

@jaejoong0529
Copy link
Collaborator

@jaejoong0529 jaejoong0529 commented Dec 22, 2025

🔎 작업 내용

  • 11개 질문의 선택지 저장, Q7 선호 활동은 다중 선택(택3) 지원 (ElementCollection)
  • CodedEnum 방식으로 API는 Integer 코드를 받아 Enum으로 변환
  • 성향점수 계산 초기값 5.0점에서 시작하는 가감점 시스템
  • R/W/S 3개 축의 High(≥5.0) / Low(<5.0) 조합으로 8가지 아바타 매칭
  • 각 아바타별 상세 프로필 정보 제공 (personality, strength, tip)

➕ 이슈 링크

📸 스크린샷

😎 리뷰 요구사항

CodedEnum방식에 대해서 어떻게 생각하세요??

🧑‍💻 예정 작업

📝 체크리스트

  • 브랜치 이름은 feature/개발내용 형식으로 작성했는가?
  • 커밋 메시지는 feat: 커밋 내용 형식으로 작성했는가?
  • 작업 전 Issue를 작성했는가?
  • dev 브랜치로 병합을 요청했는가?
  • 불필요한 주석과 공백은 제거했는가?
  • 코드 리뷰어의 피드백을 반영했는가?
  • 테스트를 진행했는가?
  • 테스트 코드를 작성했는가?

shinheekim and others added 18 commits December 20, 2025 21:28
- Profile 엔티티 생성 (nickname, profileImage, gender, phoneNumber, birthday, selfIntroduction)
- User와 OneToOne 관계 설정
- ErrorCode에 PROFILE_NOT_FOUND 추가
- ProfileRepository 인터페이스 생성
- findByUser, findByUser_Id 메서드 추가
- existsByNickname, existsByPhoneNumber 메서드 추가
- User 엔티티에서 nickname, profileImage, gender, phoneNumber, birthday 제거
- User와 Profile을 OneToOne 관계로 연결 (mappedBy 사용)
- UserRepository에서 existsByNickname, existsByPhoneNumber 제거
- UserController → ProfileController, UserApiDocs → ProfileApiDocs
- UserService → NicknameService
- UserUpdateNicknameRequest → NicknameUpdateRequest
- UserCheckNicknameResponse → NicknameValidateResponse
- Gender, AgeRange enum을 profile.domain.enums로 이동
- InvalidGenderException을 profile.exception으로 이동
- 닉네임 관련 utils, validator를 profile 패키지로 이동
- NicknameService에서 ProfileRepository 사용하도록 수정
- getProfileByUserId 메서드 버그 수정 (findById → findByUser_Id)
- createNewUser 메서드에서 User 생성 후 Profile도 함께 생성
- OAuth에서 받은 profileImage를 Profile에 저장
- nickname, gender, phoneNumber, birthday는 null로 초기화
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive travel survey system that analyzes user preferences through 11 questions and matches them to one of 8 avatar types based on calculated tendency scores. The implementation replaces the previous simple survey storage system with a sophisticated scoring algorithm.

Key Changes:

  • Introduced CodedEnum-based API design where clients send integer codes (1, 2, 3, etc.) that are converted to strongly-typed enums
  • Implemented a scoring algorithm starting from 5.0 baseline with question-specific adjustments across 4 dimensions (R/W/S/P)
  • Created avatar matching system using R/W/S thresholds to categorize users into 8 distinct travel personas with detailed profiles

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 31 comments.

Show a summary per file
File Description
TravelTendencyCalculator.java Core scoring engine that processes 11 questions and calculates tendency scores with clamping to 0.0-10.0 range
AvatarMatcher.java Maps R/W/S scores to 8 avatar types using threshold-based logic (≥5.0 = high, <5.0 = low)
AvatarProfileProvider.java Provides detailed personality profiles, strengths, and tips for each avatar type
SurveyService.java Orchestrates survey submission, score calculation, avatar matching, and result retrieval
Survey.java Domain entity storing user's 11 question responses with ElementCollection for multi-select Q7
TravelTendency.java Domain entity storing calculated R/W/S/P scores and matched avatar type
SurveyConverter.java Converts integer codes from API requests to enum types using EnumUtils
SurveyRequest.java DTO with validation for 11 questions, requiring exactly 3 selections for Q7
SurveyResponse.java DTO returning scores, avatar details, and profile information
Question* enums 11 enum types implementing CodedEnum for type-safe answer handling
AvatarType.java Enum defining 8 avatar types with codes, names, and descriptions
SurveyController.java REST endpoints for survey submission and result retrieval
SurveyApiDocs.java Comprehensive Swagger documentation with request/response examples
Deleted files Removed old survey system (TravelSurvey entities, validators, separate interest table)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (survey.getQ8Planning() == Question8Planning.DETAILED) {
r -= 2.0;
} else if (survey.getQ8Planning() == Question8Planning.FLEXIBLE) {
} else if (survey.getQ8Planning() == Question8Planning.ON_SITE) {
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement has an empty branch for FLEXIBLE. Consider adding a comment to explicitly indicate this is intentional (no score adjustment for flexible planning) to improve code clarity.

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +55
public AvatarProfile getProfile(AvatarType avatarType) {
return PROFILES.get(avatarType);
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getProfile method doesn't handle null avatarType. If a null is passed, Map.get() will return null. Consider adding a null check or returning a default profile to prevent potential NullPointerException in consuming code.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +156
public TendencyScores calculate(Survey survey) {
double r = INITIAL_SCORE; // 여행 리듬
double w = INITIAL_SCORE; // 지갑 성향
double s = INITIAL_SCORE; // 동행 스타일
double p = INITIAL_SCORE; // 활동 에너지

// Q1. 이동수단
if (survey.getQ1Transport() == Question1Transport.WALK_BUS) {
w -= 1.0;
p += 1.0;
} else if (survey.getQ1Transport() == Question1Transport.TAXI) {
w += 1.0;
p -= 1.0;
}

// Q2. 웨이팅
if (survey.getQ2Waiting() == Question2Waiting.WAIT) {
r += 1.0;
p += 1.0;
} else if (survey.getQ2Waiting() == Question2Waiting.MOVE_ELSEWHERE) {
r -= 1.0;
p -= 1.0;
}

// Q3. 숙소
if (survey.getQ3Stay() == Question3Stay.HOTEL) {
r -= 1.0;
w += 1.5;
} else if (survey.getQ3Stay() == Question3Stay.JUST_SLEEP) {
r += 1.0;
w -= 1.5;
}

// Q4. 기상시간
if (survey.getQ4Wakeup() == Question4Wakeup.EARLY) {
p += 2.0;
} else if (survey.getQ4Wakeup() == Question4Wakeup.RELAXED) {
p -= 2.0;
}

// Q5. 경비관리
if (survey.getQ5Expense() == Question5Expense.EACH_PAYS) {
w -= 1.0;
s -= 0.5;
} else if (survey.getQ5Expense() == Question5Expense.POOLED) {
w += 1.0;
s += 0.5;
}

// Q6. 소비태도
if (survey.getQ6Spend() == Question6Spend.SPLURGE) {
w += 2.0;
} else if (survey.getQ6Spend() == Question6Spend.SAVE) {
w -= 2.0;
}

// Q7. 선호활동 (택3 누적합산)
List<Question7Interest> interests = survey.getQ7Interests();
if (interests != null) {
for (Question7Interest interest : interests) {
switch (interest) {
case SIGHTSEEING:
p += 0.5;
break;
case EXHIBITION:
p -= 0.5;
break;
case NATURE:
p -= 0.5;
break;
case FOOD:
w += 0.5;
break;
case SHOPPING:
w += 1.0;
break;
case RESORT:
p -= 1.0;
break;
case ACTIVITY:
r += 1.0;
p += 0.5;
break;
case THEME_PARK:
r += 0.5;
p += 0.5;
break;
case FESTIVAL:
r += 1.0;
p += 1.0;
break;
}
}
}

// Q8. 계획성
if (survey.getQ8Planning() == Question8Planning.DETAILED) {
r -= 2.0;
} else if (survey.getQ8Planning() == Question8Planning.FLEXIBLE) {
} else if (survey.getQ8Planning() == Question8Planning.ON_SITE) {
r += 2.0;
}

// Q9. 낯선메뉴
if (survey.getQ9Menu() == Question9Menu.SAFE) {
r -= 1.5;
} else if (survey.getQ9Menu() == Question9Menu.CHECK_REVIEW) {
r -= 0.5;
} else if (survey.getQ9Menu() == Question9Menu.CHALLENGE) {
r += 1.5;
}

// Q10. 동행제안
if (survey.getQ10Companion() == Question10Companion.WELCOME) {
s += 3.0;
} else if (survey.getQ10Companion() == Question10Companion.SITUATIONAL) {
s += 1.0;
} else if (survey.getQ10Companion() == Question10Companion.US_ONLY) {
s -= 2.0;
}

// Q11. 사진
if (survey.getQ11Photo() == Question11Photo.LIFETIME_SHOT) {
s += 1.0;
p += 1.0;
} else if (survey.getQ11Photo() == Question11Photo.MATCH_COMPANION) {
s += 0.5;
} else if (survey.getQ11Photo() == Question11Photo.EYES_ONLY) {
s -= 1.0;
p -= 1.0;
}

r = clamp(r);
w = clamp(w);
s = clamp(s);
p = clamp(p);

return new TendencyScores(r, w, s, p);
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TravelTendencyCalculator contains complex scoring logic with 11 questions and multiple conditions that would benefit from comprehensive unit tests. Consider adding tests to verify correct score calculations for different answer combinations and edge cases (e.g., all minimum answers, all maximum answers, boundary conditions at threshold 5.0).

Copilot uses AI. Check for mistakes.
Comment on lines 13 to 41
public AvatarType match(double r, double w, double s) {
// R: 5.0 미만 = 안정, 5.0 이상 = 모험
boolean isStable = r < THRESHOLD;

// W: 5.0 미만 = 가성비, 5.0 이상 = 플랙스
boolean isCostEffective = w < THRESHOLD;

// S: 5.0 미만 = 독립, 5.0 이상 = 사교
boolean isSocial = s >= THRESHOLD;

// 8가지 조합 매칭
if (isStable && !isSocial && isCostEffective) {
return AvatarType.TTUR_POGUNI; // 안정 x 독립 x 가성비
} else if (isStable && !isSocial) {
return AvatarType.TTUR_MOOD; // 안정 x 독립 x 플랙스
} else if (isStable && isCostEffective) {
return AvatarType.TTUR_MALLANGI; // 안정 x 사교 x 가성비
} else if (isStable) {
return AvatarType.TTUR_SWEET; // 안정 x 사교 x 플랙스
} else if (!isSocial && isCostEffective) {
return AvatarType.TTUR_POPO; // 모험 x 독립 x 가성비
} else if (!isSocial) {
return AvatarType.TTUR_SPARKLE; // 모험 x 독립 x 플랙스
} else if (isCostEffective) {
return AvatarType.TTUR_GLIMMING; // 모험 x 사교 x 가성비
} else {
return AvatarType.TTUR_PADO; // 모험 x 사교 x 플랙스
}
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AvatarMatcher.match method contains critical business logic for avatar type determination. Given that other services in the codebase have comprehensive test coverage, consider adding unit tests to verify all 8 avatar combinations are correctly matched based on r, w, s scores.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +18
public class TravelTendencyCalculator {

private static final double INITIAL_SCORE = 5.0;
private static final double MIN_SCORE = 0.0;
private static final double MAX_SCORE = 10.0;

public TendencyScores calculate(Survey survey) {
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation: The TravelTendencyCalculator class and its calculate method lack JavaDoc comments explaining the scoring algorithm, the meaning of r/w/s/p scores, and the score range (0.0-10.0 starting from 5.0). Consider adding comprehensive documentation for this core business logic.

Copilot uses AI. Check for mistakes.
SAVE(2, "아낀다 (가성비)");

private final int code;
private final String text;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides CodedEnum.getCode; it is advisable to add an Override annotation.

Suggested change
private final String text;
private final String text;
@Override
public int getCode() {
return code;
}

Copilot uses AI. Check for mistakes.
ON_SITE(3, "현장 결정");

private final int code;
private final String text;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides CodedEnum.getText; it is advisable to add an Override annotation.

Copilot uses AI. Check for mistakes.
ON_SITE(3, "현장 결정");

private final int code;
private final String text;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides CodedEnum.getCode; it is advisable to add an Override annotation.

Suggested change
private final String text;
private final String text;
@Override
public int getCode() {
return code;
}

Copilot uses AI. Check for mistakes.
CHECK_REVIEW(2, "후기 확인 후 결정"),
CHALLENGE(3, "바로 도전");

private final int code;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides CodedEnum.getText; it is advisable to add an Override annotation.

Suggested change
private final int code;
private final int code;
@Getter(onMethod_ = {@Override})

Copilot uses AI. Check for mistakes.
CHECK_REVIEW(2, "후기 확인 후 결정"),
CHALLENGE(3, "바로 도전");

private final int code;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method overrides CodedEnum.getCode; it is advisable to add an Override annotation.

Copilot uses AI. Check for mistakes.
shinheekim and others added 29 commits December 30, 2025 04:52
- JwtTokenProvider에 validateVerificationToken 메서드 추가
- verification token의 유효성 검증 및 전화번호 일치 여부 확인
- setupInitialProfile 메서드 추가
- marketingAgree 필드 제거
- 닉네임, 성별, 전화번호, 생년월일 설정 기능 구현
- ProfileSetupRequest DTO 생성
- nickname, gender, phoneNumber, birthday, verificationToken 필드 포함
- marketingAgree 필드 제거
- setupInitialProfile 메서드 구현
- 닉네임 중복 검증
- 전화번호 인증 토큰 검증
- 전화번호 중복 검증
- 생년월일 파싱 로직 추가
- marketingAgree 관련 로직 제거
- 불필요한 주석 제거
- PUT /api/v1/me/profile 엔드포인트 추가
- PATCH에서 PUT으로 변경
- ProfileSetupRequest로 프로필 정보 받아 처리
- NicknameService -> ProfileService로 변경된 것에 맞춰 테스트 코드 수정
- Profile이 항상 존재한다는 전제로 null 체크 제거
feat: 프로필 초기 설정 API 구현
# Conflicts:
#	src/main/java/com/dduru/gildongmu/verification/controller/PhoneVerificationApiDocs.java
…iomize

docs: 에러 응답 커스터마이징 및 프로필 controller 적용
…G-MU/ddu-ru-backend into feat/#125-survey-avatar-matching

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
@shinheekim shinheekim merged commit 041462c into dev Dec 30, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔧 BE ✨ feature 새 기능 개발 / 요구사항

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 여행 설문조사 기반 아바타 매칭 시스템 구현

3 participants