feat(recommend): 추천 점수 산식 상수를 분리한다
This commit is contained in:
@@ -18,16 +18,22 @@ class RecommendationScorePolicy {
|
|||||||
communicationScore: Long,
|
communicationScore: Long,
|
||||||
newBoost: Double
|
newBoost: Double
|
||||||
): Double {
|
): Double {
|
||||||
return ((followIncrease * 0.35) + (contentActivityScore * 0.3) + (communicationScore * 0.2)) * newBoost
|
return (
|
||||||
|
(followIncrease * RecommendationScoreSpec.DEBUT_FOLLOW_INCREASE_WEIGHT) +
|
||||||
|
(contentActivityScore * RecommendationScoreSpec.DEBUT_CONTENT_ACTIVITY_WEIGHT) +
|
||||||
|
(communicationScore * RecommendationScoreSpec.DEBUT_COMMUNICATION_WEIGHT)
|
||||||
|
) * newBoost
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateAiChatScore(
|
fun calculateAiChatScore(
|
||||||
recentChatCount: Long,
|
recentChatCount: Long,
|
||||||
recentActiveUserCount: Long,
|
recentActiveUserCount: Long,
|
||||||
followIncrease: Long,
|
|
||||||
newBoost: Double
|
newBoost: Double
|
||||||
): Double {
|
): Double {
|
||||||
return ((0.45 * recentChatCount) + (0.35 * recentActiveUserCount) + (0.20 * followIncrease)) * newBoost
|
return (
|
||||||
|
(RecommendationScoreSpec.AI_RECENT_CHAT_WEIGHT * recentChatCount) +
|
||||||
|
(RecommendationScoreSpec.AI_RECENT_ACTIVE_USER_WEIGHT * recentActiveUserCount)
|
||||||
|
) * newBoost
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateCheerScore(
|
fun calculateCheerScore(
|
||||||
@@ -36,7 +42,11 @@ class RecommendationScorePolicy {
|
|||||||
donationCount: Long,
|
donationCount: Long,
|
||||||
newBoost: Double
|
newBoost: Double
|
||||||
): Double {
|
): Double {
|
||||||
return ((0.6 * donationAmount) + (0.3 * fanTalkCount) + (0.1 * donationCount)) * newBoost
|
return (
|
||||||
|
(RecommendationScoreSpec.CHEER_DONATION_AMOUNT_WEIGHT * donationAmount) +
|
||||||
|
(RecommendationScoreSpec.CHEER_FAN_TALK_WEIGHT * fanTalkCount) +
|
||||||
|
(RecommendationScoreSpec.CHEER_DONATION_COUNT_WEIGHT * donationCount)
|
||||||
|
) * newBoost
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateCommunityScore(
|
fun calculateCommunityScore(
|
||||||
@@ -45,7 +55,11 @@ class RecommendationScorePolicy {
|
|||||||
followerCount: Long,
|
followerCount: Long,
|
||||||
newBoost: Double
|
newBoost: Double
|
||||||
): Double {
|
): Double {
|
||||||
return ((0.5 * likeCount) + (0.5 * commentCount) + (0.1 * followerCount)) * newBoost
|
return (
|
||||||
|
(RecommendationScoreSpec.COMMUNITY_LIKE_WEIGHT * likeCount) +
|
||||||
|
(RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT * commentCount) +
|
||||||
|
(RecommendationScoreSpec.COMMUNITY_FOLLOWER_WEIGHT * followerCount)
|
||||||
|
) * newBoost
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateFirstAudioRecencyScore(releaseDate: LocalDateTime, now: LocalDateTime): Int {
|
fun calculateFirstAudioRecencyScore(releaseDate: LocalDateTime, now: LocalDateTime): Int {
|
||||||
@@ -63,10 +77,10 @@ class RecommendationScorePolicy {
|
|||||||
private fun calculateNewBoost(baseAt: LocalDateTime, now: LocalDateTime): Double {
|
private fun calculateNewBoost(baseAt: LocalDateTime, now: LocalDateTime): Double {
|
||||||
val days = ChronoUnit.DAYS.between(baseAt.toLocalDate(), now.toLocalDate())
|
val days = ChronoUnit.DAYS.between(baseAt.toLocalDate(), now.toLocalDate())
|
||||||
return when {
|
return when {
|
||||||
days <= 10 -> 1.5
|
days <= RecommendationScoreSpec.NEW_BOOST_10_DAY_LIMIT -> RecommendationScoreSpec.NEW_BOOST_10_DAYS
|
||||||
days <= 20 -> 1.3
|
days <= RecommendationScoreSpec.NEW_BOOST_20_DAY_LIMIT -> RecommendationScoreSpec.NEW_BOOST_20_DAYS
|
||||||
days <= 30 -> 1.2
|
days <= RecommendationScoreSpec.NEW_BOOST_30_DAY_LIMIT -> RecommendationScoreSpec.NEW_BOOST_30_DAYS
|
||||||
else -> 1.0
|
else -> RecommendationScoreSpec.DEFAULT_NEW_BOOST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.recommend.domain
|
||||||
|
|
||||||
|
object RecommendationScoreSpec {
|
||||||
|
const val NEW_BOOST_10_DAY_LIMIT = 10L
|
||||||
|
const val NEW_BOOST_20_DAY_LIMIT = 20L
|
||||||
|
const val NEW_BOOST_30_DAY_LIMIT = 30L
|
||||||
|
|
||||||
|
const val DEBUT_FOLLOW_INCREASE_WEIGHT = 0.35
|
||||||
|
const val DEBUT_CONTENT_ACTIVITY_WEIGHT = 0.3
|
||||||
|
const val DEBUT_COMMUNICATION_WEIGHT = 0.2
|
||||||
|
|
||||||
|
const val AI_RECENT_CHAT_WEIGHT = 0.45
|
||||||
|
const val AI_RECENT_ACTIVE_USER_WEIGHT = 0.35
|
||||||
|
|
||||||
|
const val CHEER_DONATION_AMOUNT_WEIGHT = 0.6
|
||||||
|
const val CHEER_FAN_TALK_WEIGHT = 0.3
|
||||||
|
const val CHEER_DONATION_COUNT_WEIGHT = 0.1
|
||||||
|
|
||||||
|
const val COMMUNITY_LIKE_WEIGHT = 0.5
|
||||||
|
const val COMMUNITY_COMMENT_WEIGHT = 0.5
|
||||||
|
const val COMMUNITY_FOLLOWER_WEIGHT = 0.1
|
||||||
|
|
||||||
|
const val NEW_BOOST_10_DAYS = 1.5
|
||||||
|
const val NEW_BOOST_20_DAYS = 1.3
|
||||||
|
const val NEW_BOOST_30_DAYS = 1.2
|
||||||
|
const val DEFAULT_NEW_BOOST = 1.0
|
||||||
|
}
|
||||||
@@ -13,6 +13,9 @@ class RecommendationScorePolicyTest {
|
|||||||
fun shouldApplyCreatorNewBoostByDebutDays() {
|
fun shouldApplyCreatorNewBoostByDebutDays() {
|
||||||
val now = LocalDateTime.of(2026, 5, 30, 12, 0)
|
val now = LocalDateTime.of(2026, 5, 30, 12, 0)
|
||||||
|
|
||||||
|
assertEquals(10L, RecommendationScoreSpec.NEW_BOOST_10_DAY_LIMIT)
|
||||||
|
assertEquals(20L, RecommendationScoreSpec.NEW_BOOST_20_DAY_LIMIT)
|
||||||
|
assertEquals(30L, RecommendationScoreSpec.NEW_BOOST_30_DAY_LIMIT)
|
||||||
assertEquals(1.5, policy.calculateCreatorNewBoost(now.minusDays(10), now), 0.0001)
|
assertEquals(1.5, policy.calculateCreatorNewBoost(now.minusDays(10), now), 0.0001)
|
||||||
assertEquals(1.3, policy.calculateCreatorNewBoost(now.minusDays(20), now), 0.0001)
|
assertEquals(1.3, policy.calculateCreatorNewBoost(now.minusDays(20), now), 0.0001)
|
||||||
assertEquals(1.2, policy.calculateCreatorNewBoost(now.minusDays(30), now), 0.0001)
|
assertEquals(1.2, policy.calculateCreatorNewBoost(now.minusDays(30), now), 0.0001)
|
||||||
@@ -33,6 +36,10 @@ class RecommendationScorePolicyTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("최근 데뷔 크리에이터 추천 점수는 PRD 가중치와 신규 부스트를 적용한다")
|
@DisplayName("최근 데뷔 크리에이터 추천 점수는 PRD 가중치와 신규 부스트를 적용한다")
|
||||||
fun shouldCalculateDebutCreatorScore() {
|
fun shouldCalculateDebutCreatorScore() {
|
||||||
|
assertEquals(0.35, RecommendationScoreSpec.DEBUT_FOLLOW_INCREASE_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.3, RecommendationScoreSpec.DEBUT_CONTENT_ACTIVITY_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.2, RecommendationScoreSpec.DEBUT_COMMUNICATION_WEIGHT, 0.0001)
|
||||||
|
|
||||||
val score = policy.calculateDebutCreatorScore(
|
val score = policy.calculateDebutCreatorScore(
|
||||||
followIncrease = 10,
|
followIncrease = 10,
|
||||||
contentActivityScore = 20,
|
contentActivityScore = 20,
|
||||||
@@ -44,21 +51,27 @@ class RecommendationScorePolicyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("AI 채팅 추천 점수는 PRD 가중치와 신규 부스트를 적용한다")
|
@DisplayName("AI 채팅 추천 점수는 이번 스프린트에서 팔로우 증가량을 제외한다")
|
||||||
fun shouldCalculateAiChatScore() {
|
fun shouldCalculateAiChatScore() {
|
||||||
|
assertEquals(0.45, RecommendationScoreSpec.AI_RECENT_CHAT_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.35, RecommendationScoreSpec.AI_RECENT_ACTIVE_USER_WEIGHT, 0.0001)
|
||||||
|
|
||||||
val score = policy.calculateAiChatScore(
|
val score = policy.calculateAiChatScore(
|
||||||
recentChatCount = 100,
|
recentChatCount = 100,
|
||||||
recentActiveUserCount = 20,
|
recentActiveUserCount = 20,
|
||||||
followIncrease = 10,
|
|
||||||
newBoost = 1.3
|
newBoost = 1.3
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(70.2, score, 0.0001)
|
assertEquals(67.6, score, 0.0001)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("최근 응원 추천 점수는 후원 금액, 팬 Talk 수, 후원 수에 가중치를 적용한다")
|
@DisplayName("최근 응원 추천 점수는 후원 금액, 팬 Talk 수, 후원 수에 가중치를 적용한다")
|
||||||
fun shouldCalculateCheerScore() {
|
fun shouldCalculateCheerScore() {
|
||||||
|
assertEquals(0.6, RecommendationScoreSpec.CHEER_DONATION_AMOUNT_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.3, RecommendationScoreSpec.CHEER_FAN_TALK_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.1, RecommendationScoreSpec.CHEER_DONATION_COUNT_WEIGHT, 0.0001)
|
||||||
|
|
||||||
val score = policy.calculateCheerScore(
|
val score = policy.calculateCheerScore(
|
||||||
donationAmount = 1000,
|
donationAmount = 1000,
|
||||||
fanTalkCount = 20,
|
fanTalkCount = 20,
|
||||||
@@ -72,6 +85,10 @@ class RecommendationScorePolicyTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("인기 커뮤니티 점수는 좋아요 수, 댓글 수, 팔로우 수에 가중치를 적용한다")
|
@DisplayName("인기 커뮤니티 점수는 좋아요 수, 댓글 수, 팔로우 수에 가중치를 적용한다")
|
||||||
fun shouldCalculateCommunityScore() {
|
fun shouldCalculateCommunityScore() {
|
||||||
|
assertEquals(0.5, RecommendationScoreSpec.COMMUNITY_LIKE_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.5, RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT, 0.0001)
|
||||||
|
assertEquals(0.1, RecommendationScoreSpec.COMMUNITY_FOLLOWER_WEIGHT, 0.0001)
|
||||||
|
|
||||||
val score = policy.calculateCommunityScore(
|
val score = policy.calculateCommunityScore(
|
||||||
likeCount = 40,
|
likeCount = 40,
|
||||||
commentCount = 20,
|
commentCount = 20,
|
||||||
|
|||||||
Reference in New Issue
Block a user