docs(home): 인기 커뮤니티 스냅샷 산식을 문서화한다
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## 시나리오 계약
|
||||
- Happy path: 홈 API 호출 시각 기준 KST 전날 대상일 `snapshotAt`을 계산하고, 대상일 포함 최근 7일 데이터를 UTC half-open 범위로 변환해 `POPULAR_COMMUNITY` 점수를 계산한 뒤 점수순 상위 20개 스냅샷을 저장한다. Real surface: `RecommendationSnapshotWindowPolicyTest`, `DefaultHomeRecommendationQueryRepositoryTest`, `RecommendationSnapshotRefreshServiceTest`.
|
||||
- Score: 인기 커뮤니티 점수는 `((likeCount * 0.50) + (commentCount * 0.40) + (creatorFollowerCount * 0.10)) * newBoost`다. `creatorFollowerCount`는 스냅샷 생성 시점의 활성 팔로워 총수다. Real surface: `RecommendationScorePolicyTest`, `DefaultHomeRecommendationQueryRepositoryTest`.
|
||||
- Score: 인기 커뮤니티 점수는 `((likeCount * 0.50) + (commentCount * 0.50)) * newBoost`다. 후보는 좋아요가 있거나 산식에 반영 가능한 댓글이 있는 게시글이다. Real surface: `RecommendationScorePolicyTest`, `DefaultHomeRecommendationQueryRepositoryTest`.
|
||||
- Exclusion: 공지/고정 게시글(`CreatorCommunity.isFixed`/`is_fixed = true`)과 유료 게시글(`price > 0`)은 스냅샷 후보와 상세 조회 결과에서 제외한다. Real surface: `DefaultHomeRecommendationQueryRepositoryTest`, `HomeRecommendationQueryServiceTest`.
|
||||
- Boost: 신규 부스트는 크리에이터 데뷔일 기준 0~10일 `1.15`, 11~20일 `1.10`, 21~30일 `1.05`, 31일 이상 `1.0`이다. Real surface: `RecommendationScorePolicyTest`, `DefaultHomeRecommendationQueryRepositoryTest`.
|
||||
- Daily freshness: 과거 최신 `POPULAR_COMMUNITY` 스냅샷이 있어도 홈 API 호출 기준 대상일 `snapshotAt` 스냅샷이 없으면 fallback refresh를 시도한다. Real surface: `HomeRecommendationQueryServiceTest`, `RecommendationSnapshotFallbackServiceTest`.
|
||||
@@ -26,7 +26,7 @@
|
||||
- 유지: 스냅샷 후보 저장 수 최대 20개, 홈 첫 화면 반환 수 최대 10개를 유지한다.
|
||||
- 유지: 상세 조회 시점의 활성 게시글/크리에이터 필터, 성인 필터, 차단 필터, 크리에이터 중복 제거 정책을 유지한다.
|
||||
- 유지: 댓글 불가 게시글은 댓글 수를 0으로 계산한다.
|
||||
- 변경: 댓글 가중치를 `0.50`에서 `0.40`으로 바꾼다.
|
||||
- 변경: 기존 팔로워 가중치를 제거하고 댓글 가중치에 합산한다.
|
||||
- 변경: 신규 부스트는 기존 크리에이터 공통 부스트 `1.5/1.3/1.2`가 아니라 `POPULAR_COMMUNITY` 전용 `1.15/1.10/1.05`를 사용한다.
|
||||
- 변경: 좋아요/댓글 집계 기간은 UTC half-open 최근 7일 window를 사용한다.
|
||||
- 변경: 게시글 자체 생성 시각 조건도 대상일 `windowEndExclusiveUtc` 이전으로 맞춘다.
|
||||
@@ -65,17 +65,16 @@
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt`
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt`
|
||||
- RED: `COMMUNITY_LIKE_WEIGHT = 0.50`, `COMMUNITY_COMMENT_WEIGHT = 0.40`, `COMMUNITY_FOLLOWER_WEIGHT = 0.10`을 기대하는 실패 테스트를 작성한다. `calculateCommunityScore(...)`가 세 입력값과 부스트를 PRD 산식대로 계산하는지도 검증한다.
|
||||
- RED: `COMMUNITY_LIKE_WEIGHT = 0.50`, `COMMUNITY_COMMENT_WEIGHT = 0.50`을 기대하는 실패 테스트를 작성한다. `calculateCommunityScore(...)`가 좋아요/댓글 입력값과 부스트를 PRD 산식대로 계산하는지도 검증한다.
|
||||
- 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.domain.RecommendationScorePolicyTest`
|
||||
- GREEN: 인기 커뮤니티 점수 상수를 PRD 값으로 변경하고 기존 `calculateCommunityScore(...)`가 같은 상수를 사용하게 한다.
|
||||
- REFACTOR: 기존 AI/최근 데뷔/응원 크리에이터 상수와 함수 값은 변경하지 않았는지 같은 테스트 안에서 회귀 assertion을 유지한다.
|
||||
- 계산식 테스트 케이스:
|
||||
- `likeCount=0`, `commentCount=0`, `followerCount=0`, `newBoost=1.0`이면 `0.0`
|
||||
- `likeCount=10`, `commentCount=0`, `followerCount=0`, `newBoost=1.0`이면 `5.0`
|
||||
- `likeCount=0`, `commentCount=10`, `followerCount=0`, `newBoost=1.0`이면 `4.0`
|
||||
- `likeCount=0`, `commentCount=0`, `followerCount=10`, `newBoost=1.0`이면 `1.0`
|
||||
- `likeCount=40`, `commentCount=20`, `followerCount=100`, `newBoost=1.0`이면 `38.0`
|
||||
- `likeCount=40`, `commentCount=20`, `followerCount=100`, `newBoost=1.15`이면 `43.7`
|
||||
- `likeCount=0`, `commentCount=0`, `newBoost=1.0`이면 `0.0`
|
||||
- `likeCount=10`, `commentCount=0`, `newBoost=1.0`이면 `5.0`
|
||||
- `likeCount=0`, `commentCount=10`, `newBoost=1.0`이면 `5.0`
|
||||
- `likeCount=40`, `commentCount=20`, `newBoost=1.0`이면 `30.0`
|
||||
- `likeCount=40`, `commentCount=20`, `newBoost=1.15`이면 `34.5`
|
||||
- 기대 결과: 인기 커뮤니티 점수 산식 근거가 Kotlin 단위 테스트로 고정된다.
|
||||
|
||||
- [x] **Task 2.2: 인기 커뮤니티 신규 부스트 경계값 테스트 추가**
|
||||
@@ -115,15 +114,15 @@
|
||||
- REFACTOR: `HomeRecommendationQueryPort.findPopularCommunitySnapshots(...)` 시그니처는 `windowEndExclusiveUtc` 의미가 드러나도록 정리한다.
|
||||
- 기대 결과: 스케줄러와 fallback이 같은 `POPULAR_COMMUNITY` 최근 7일 refresh 경로를 호출할 수 있다.
|
||||
|
||||
- [x] **Task 3.2: 좋아요/댓글/팔로워 집계 기준 변경**
|
||||
- [x] **Task 3.2: 좋아요/댓글 집계 기준 변경**
|
||||
- 파일 경로:
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt`
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt`
|
||||
- RED: 활성 좋아요/댓글만 최근 7일 half-open window 안에서 distinct id로 집계하고, `creatorFollowerCount`는 기간 조건 없이 스냅샷 생성 시점의 활성 팔로워 총수로 집계하는 실패 테스트를 작성한다.
|
||||
- RED: 활성 좋아요/댓글만 최근 7일 half-open window 안에서 distinct id로 집계하고, 팔로워 수는 스냅샷 후보와 점수에 반영하지 않는 실패 테스트를 작성한다.
|
||||
- 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest`
|
||||
- GREEN: `like_stats`, `comment_stats`, `follower_stats` native query를 PRD 기준으로 수정한다. 좋아요/댓글은 `created_at >= :windowStartUtc and created_at < :windowEndExclusiveUtc`, 팔로워는 `is_active = true` 총수로 계산한다.
|
||||
- GREEN: `like_stats`, `comment_stats` native query를 PRD 기준으로 수정한다. 좋아요/댓글은 `created_at >= :windowStartUtc and created_at < :windowEndExclusiveUtc`로 계산하고, follower CTE와 점수 항은 두지 않는다.
|
||||
- REFACTOR: 댓글 불가 게시글은 기존처럼 `commentCount = 0`으로 계산하는 조건을 유지한다.
|
||||
- 기대 결과: 세 metric의 기간/활성 조건이 서로 다른 의도대로 고정된다.
|
||||
- 기대 결과: 좋아요/댓글 metric의 기간/활성 조건이 의도대로 고정되고 follower-only 게시글은 제외된다.
|
||||
|
||||
- [x] **Task 3.3: 공지/고정 및 유료 게시글 제외 조건 고정**
|
||||
- 파일 경로:
|
||||
@@ -149,7 +148,7 @@
|
||||
- 파일 경로:
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt`
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt`
|
||||
- RED: 좋아요/댓글/팔로워 수가 모두 0인 게시글 제외, 데뷔일이 없는 크리에이터 제외, `created_at >= windowEndExclusiveUtc` 게시글 제외, 점수 내림차순/`randomTieBreaker` 오름차순/limit 20 동작을 검증하는 실패 테스트를 작성한다.
|
||||
- RED: 좋아요와 산식 반영 가능 댓글 수가 모두 0인 게시글 제외, 데뷔일이 없는 크리에이터 제외, `created_at >= windowEndExclusiveUtc` 게시글 제외, 점수 내림차순/`randomTieBreaker` 오름차순/limit 20 동작을 검증하는 실패 테스트를 작성한다.
|
||||
- 실패 확인: `./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest`
|
||||
- GREEN: 기존 후보 제외 조건과 정렬/limit을 PRD 기준에 맞게 유지 또는 보정한다.
|
||||
- REFACTOR: AI 캐릭터와 응원 크리에이터 스냅샷 query가 영향받지 않았는지 focused test로 확인한다.
|
||||
@@ -309,7 +308,7 @@
|
||||
|
||||
- 2026-07-10: 리뷰 게이트 후 차단 이슈 수정 및 재검증 통과.
|
||||
- 리뷰 지적: `POPULAR_COMMUNITY` 0점 후보 제외 누락, 최근 데뷔 크리에이터 query에 커뮤니티 전용 부스트 상수 침투.
|
||||
- 수정: 인기 커뮤니티 후보에 좋아요/댓글/팔로워 중 하나 이상 존재 조건 추가, 최근 데뷔 크리에이터는 기존 `NEW_BOOST_*` 상수로 복원.
|
||||
- 수정: 인기 커뮤니티 후보에 좋아요/댓글 중 하나 이상 존재 조건 추가, 최근 데뷔 크리에이터는 기존 `NEW_BOOST_*` 상수로 복원.
|
||||
- 회귀 테스트 추가: 0점 인기 커뮤니티 후보 제외, 최근 데뷔 크리에이터 기존 신규 부스트 유지.
|
||||
- `./gradlew ktlintCheck` 성공.
|
||||
- `./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest` 성공.
|
||||
|
||||
Reference in New Issue
Block a user