feat(recommend): 홈 추천 점수 정책을 추가한다
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package kr.co.vividnext.sodalive.v2.recommend.domain
|
||||
|
||||
import java.time.LocalDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class RecommendationScorePolicy {
|
||||
fun calculateCreatorNewBoost(debutAt: LocalDateTime, now: LocalDateTime): Double {
|
||||
return calculateNewBoost(debutAt, now)
|
||||
}
|
||||
|
||||
fun calculateAiCharacterNewBoost(createdAt: LocalDateTime, now: LocalDateTime): Double {
|
||||
return calculateNewBoost(createdAt, now)
|
||||
}
|
||||
|
||||
fun calculateDebutCreatorScore(
|
||||
followIncrease: Long,
|
||||
contentActivityScore: Long,
|
||||
communicationScore: Long,
|
||||
newBoost: Double
|
||||
): Double {
|
||||
return ((followIncrease * 0.35) + (contentActivityScore * 0.3) + (communicationScore * 0.2)) * newBoost
|
||||
}
|
||||
|
||||
fun calculateAiChatScore(
|
||||
recentChatCount: Long,
|
||||
recentActiveUserCount: Long,
|
||||
followIncrease: Long,
|
||||
newBoost: Double
|
||||
): Double {
|
||||
return ((0.45 * recentChatCount) + (0.35 * recentActiveUserCount) + (0.20 * followIncrease)) * newBoost
|
||||
}
|
||||
|
||||
fun calculateCheerScore(
|
||||
donationAmount: Long,
|
||||
fanTalkCount: Long,
|
||||
donationCount: Long,
|
||||
newBoost: Double
|
||||
): Double {
|
||||
return ((0.6 * donationAmount) + (0.3 * fanTalkCount) + (0.1 * donationCount)) * newBoost
|
||||
}
|
||||
|
||||
fun calculateCommunityScore(
|
||||
likeCount: Long,
|
||||
commentCount: Long,
|
||||
followerCount: Long,
|
||||
newBoost: Double
|
||||
): Double {
|
||||
return ((0.5 * likeCount) + (0.5 * commentCount) + (0.1 * followerCount)) * newBoost
|
||||
}
|
||||
|
||||
fun calculateFirstAudioRecencyScore(releaseDate: LocalDateTime, now: LocalDateTime): Int {
|
||||
val days = ChronoUnit.DAYS.between(releaseDate.toLocalDate(), now.toLocalDate())
|
||||
return when {
|
||||
days <= 3 -> 100
|
||||
days <= 7 -> 80
|
||||
days <= 14 -> 60
|
||||
days <= 21 -> 40
|
||||
days <= 30 -> 20
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateNewBoost(baseAt: LocalDateTime, now: LocalDateTime): Double {
|
||||
val days = ChronoUnit.DAYS.between(baseAt.toLocalDate(), now.toLocalDate())
|
||||
return when {
|
||||
days <= 10 -> 1.5
|
||||
days <= 20 -> 1.3
|
||||
days <= 30 -> 1.2
|
||||
else -> 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package kr.co.vividnext.sodalive.v2.recommend.domain
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDateTime
|
||||
|
||||
class RecommendationScorePolicyTest {
|
||||
private val policy = RecommendationScorePolicy()
|
||||
|
||||
@Test
|
||||
@DisplayName("데뷔일 기준 신규 부스트는 10일/20일/30일 구간을 적용한다")
|
||||
fun shouldApplyCreatorNewBoostByDebutDays() {
|
||||
val now = LocalDateTime.of(2026, 5, 30, 12, 0)
|
||||
|
||||
assertEquals(1.5, policy.calculateCreatorNewBoost(now.minusDays(10), 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.0, policy.calculateCreatorNewBoost(now.minusDays(31), now), 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("AI 캐릭터 생성일 기준 신규 부스트는 10일/20일/30일 구간을 적용한다")
|
||||
fun shouldApplyAiCharacterNewBoostByCreatedDays() {
|
||||
val now = LocalDateTime.of(2026, 5, 30, 12, 0)
|
||||
|
||||
assertEquals(1.5, policy.calculateAiCharacterNewBoost(now.minusDays(10), now), 0.0001)
|
||||
assertEquals(1.3, policy.calculateAiCharacterNewBoost(now.minusDays(20), now), 0.0001)
|
||||
assertEquals(1.2, policy.calculateAiCharacterNewBoost(now.minusDays(30), now), 0.0001)
|
||||
assertEquals(1.0, policy.calculateAiCharacterNewBoost(now.minusDays(31), now), 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 데뷔 크리에이터 추천 점수는 PRD 가중치와 신규 부스트를 적용한다")
|
||||
fun shouldCalculateDebutCreatorScore() {
|
||||
val score = policy.calculateDebutCreatorScore(
|
||||
followIncrease = 10,
|
||||
contentActivityScore = 20,
|
||||
communicationScore = 30,
|
||||
newBoost = 1.5
|
||||
)
|
||||
|
||||
assertEquals(23.25, score, 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("AI 채팅 추천 점수는 PRD 가중치와 신규 부스트를 적용한다")
|
||||
fun shouldCalculateAiChatScore() {
|
||||
val score = policy.calculateAiChatScore(
|
||||
recentChatCount = 100,
|
||||
recentActiveUserCount = 20,
|
||||
followIncrease = 10,
|
||||
newBoost = 1.3
|
||||
)
|
||||
|
||||
assertEquals(70.2, score, 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("최근 응원 추천 점수는 후원 금액, 팬 Talk 수, 후원 수에 가중치를 적용한다")
|
||||
fun shouldCalculateCheerScore() {
|
||||
val score = policy.calculateCheerScore(
|
||||
donationAmount = 1000,
|
||||
fanTalkCount = 20,
|
||||
donationCount = 10,
|
||||
newBoost = 1.2
|
||||
)
|
||||
|
||||
assertEquals(728.4, score, 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("인기 커뮤니티 점수는 좋아요 수, 댓글 수, 팔로우 수에 가중치를 적용한다")
|
||||
fun shouldCalculateCommunityScore() {
|
||||
val score = policy.calculateCommunityScore(
|
||||
likeCount = 40,
|
||||
commentCount = 20,
|
||||
followerCount = 100,
|
||||
newBoost = 1.2
|
||||
)
|
||||
|
||||
assertEquals(48.0, score, 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("첫 오디오 최신성 점수는 releaseDate 기준 경과일 구간을 적용한다")
|
||||
fun shouldCalculateFirstAudioRecencyScore() {
|
||||
val now = LocalDateTime.of(2026, 5, 30, 12, 0)
|
||||
|
||||
assertEquals(100, policy.calculateFirstAudioRecencyScore(now.minusDays(3), now))
|
||||
assertEquals(80, policy.calculateFirstAudioRecencyScore(now.minusDays(7), now))
|
||||
assertEquals(60, policy.calculateFirstAudioRecencyScore(now.minusDays(14), now))
|
||||
assertEquals(40, policy.calculateFirstAudioRecencyScore(now.minusDays(21), now))
|
||||
assertEquals(20, policy.calculateFirstAudioRecencyScore(now.minusDays(30), now))
|
||||
assertEquals(0, policy.calculateFirstAudioRecencyScore(now.minusDays(31), now))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user