diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt index 42d4b2b4..87e3ca06 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicy.kt @@ -28,6 +28,16 @@ class RecommendationScorePolicy { } } + fun calculateCommunityCreatorNewBoost(debutAt: LocalDateTime, now: LocalDateTime): Double { + val days = ChronoUnit.DAYS.between(debutAt.toLocalDate(), now.toLocalDate()) + return when { + days <= RecommendationScoreSpec.NEW_BOOST_10_DAY_LIMIT -> RecommendationScoreSpec.COMMUNITY_NEW_BOOST_10_DAYS + days <= RecommendationScoreSpec.NEW_BOOST_20_DAY_LIMIT -> RecommendationScoreSpec.COMMUNITY_NEW_BOOST_20_DAYS + days <= RecommendationScoreSpec.NEW_BOOST_30_DAY_LIMIT -> RecommendationScoreSpec.COMMUNITY_NEW_BOOST_30_DAYS + else -> RecommendationScoreSpec.DEFAULT_NEW_BOOST + } + } + fun calculateDebutCreatorScore( followIncrease: Long, contentActivityScore: Long, diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt index e4700dac..6a216e82 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScoreSpec.kt @@ -18,7 +18,7 @@ object RecommendationScoreSpec { 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_COMMENT_WEIGHT = 0.4 const val COMMUNITY_FOLLOWER_WEIGHT = 0.1 const val NEW_BOOST_10_DAYS = 1.5 @@ -30,5 +30,8 @@ object RecommendationScoreSpec { const val CHEER_NEW_BOOST_10_DAYS = 1.15 const val CHEER_NEW_BOOST_20_DAYS = 1.10 const val CHEER_NEW_BOOST_30_DAYS = 1.05 + const val COMMUNITY_NEW_BOOST_10_DAYS = 1.15 + const val COMMUNITY_NEW_BOOST_20_DAYS = 1.10 + const val COMMUNITY_NEW_BOOST_30_DAYS = 1.05 const val DEFAULT_NEW_BOOST = 1.0 } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicy.kt index bdd9dd13..56d9842e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicy.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicy.kt @@ -28,6 +28,11 @@ class RecommendationSnapshotWindowPolicy { ) } + fun previousKstSevenDayUtcWindow(nowUtc: LocalDateTime): RecommendationSnapshotWindow { + val dayWindow = previousKstDayUtcWindow(nowUtc) + return dayWindow.copy(startUtc = dayWindow.startUtc.minusDays(6)) + } + companion object { private val UTC_ZONE: ZoneId = ZoneId.of("UTC") private val KST_ZONE: ZoneId = ZoneId.of("Asia/Seoul") diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt index e1389cad..e652a561 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationScorePolicyTest.kt @@ -115,17 +115,36 @@ class RecommendationScorePolicyTest { @DisplayName("인기 커뮤니티 점수는 좋아요 수, 댓글 수, 팔로우 수에 가중치를 적용한다") fun shouldCalculateCommunityScore() { assertEquals(0.5, RecommendationScoreSpec.COMMUNITY_LIKE_WEIGHT, 0.0001) - assertEquals(0.5, RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT, 0.0001) + assertEquals(0.4, RecommendationScoreSpec.COMMUNITY_COMMENT_WEIGHT, 0.0001) assertEquals(0.1, RecommendationScoreSpec.COMMUNITY_FOLLOWER_WEIGHT, 0.0001) - val score = policy.calculateCommunityScore( - likeCount = 40, - commentCount = 20, - followerCount = 100, - newBoost = 1.2 - ) + assertEquals(0.0, policy.calculateCommunityScore(0, 0, 0, 1.0), 0.0001) + assertEquals(5.0, policy.calculateCommunityScore(10, 0, 0, 1.0), 0.0001) + assertEquals(4.0, policy.calculateCommunityScore(0, 10, 0, 1.0), 0.0001) + assertEquals(1.0, policy.calculateCommunityScore(0, 0, 10, 1.0), 0.0001) + assertEquals(38.0, policy.calculateCommunityScore(40, 20, 100, 1.0), 0.0001) + assertEquals(43.7, policy.calculateCommunityScore(40, 20, 100, 1.15), 0.0001) + } - assertEquals(48.0, score, 0.0001) + @Test + @DisplayName("인기 커뮤니티 신규 부스트는 데뷔일 기준 10일/20일/30일 구간을 적용한다") + fun shouldApplyCommunityCreatorNewBoostByDebutDays() { + val now = LocalDateTime.of(2026, 5, 30, 12, 0) + + assertEquals(1.15, RecommendationScoreSpec.COMMUNITY_NEW_BOOST_10_DAYS, 0.0001) + assertEquals(1.10, RecommendationScoreSpec.COMMUNITY_NEW_BOOST_20_DAYS, 0.0001) + assertEquals(1.05, RecommendationScoreSpec.COMMUNITY_NEW_BOOST_30_DAYS, 0.0001) + assertEquals(1.15, policy.calculateCommunityCreatorNewBoost(now, now), 0.0001) + assertEquals(1.15, policy.calculateCommunityCreatorNewBoost(now.minusDays(10), now), 0.0001) + assertEquals(1.10, policy.calculateCommunityCreatorNewBoost(now.minusDays(11), now), 0.0001) + assertEquals(1.10, policy.calculateCommunityCreatorNewBoost(now.minusDays(20), now), 0.0001) + assertEquals(1.05, policy.calculateCommunityCreatorNewBoost(now.minusDays(21), now), 0.0001) + assertEquals(1.05, policy.calculateCommunityCreatorNewBoost(now.minusDays(30), now), 0.0001) + assertEquals(1.0, policy.calculateCommunityCreatorNewBoost(now.minusDays(31), 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.2, policy.calculateCreatorNewBoost(now.minusDays(30), now), 0.0001) } @Test diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicyTest.kt index a2cefa11..2232f536 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicyTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/domain/RecommendationSnapshotWindowPolicyTest.kt @@ -17,4 +17,14 @@ class RecommendationSnapshotWindowPolicyTest { assertEquals(LocalDateTime.of(2026, 7, 9, 15, 0, 0), window.endExclusiveUtc) assertEquals(LocalDateTime.of(2026, 7, 9, 14, 59, 59), window.snapshotAt) } + + @Test + @DisplayName("KST 전날을 포함한 최근 7일을 UTC 조회 범위로 변환한다") + fun shouldConvertPreviousKstSevenDaysToUtcWindow() { + val window = policy.previousKstSevenDayUtcWindow(LocalDateTime.of(2026, 7, 10, 3, 0, 0)) + + assertEquals(LocalDateTime.of(2026, 7, 2, 15, 0, 0), window.startUtc) + assertEquals(LocalDateTime.of(2026, 7, 9, 15, 0, 0), window.endExclusiveUtc) + assertEquals(LocalDateTime.of(2026, 7, 9, 14, 59, 59), window.snapshotAt) + } }