From 30b687737e708ceef560bc70d59a86082e819e26 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 24 Jun 2026 23:46:40 +0900 Subject: [PATCH] =?UTF-8?q?feat(content-ranking):=20=EC=8A=A4=EB=83=85?= =?UTF-8?q?=EC=83=B7=20=EA=B0=B1=EC=8B=A0=EC=97=90=20=EA=B3=B5=EA=B0=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=EC=9D=84=20=EB=B0=98=EC=98=81=ED=95=9C?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreatorRankingSnapshotRefreshService.kt | 6 ++++++ ...reatorRankingSnapshotRefreshServiceTest.kt | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt index 77fb8c02..b96bbdf4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshService.kt @@ -3,6 +3,7 @@ package kr.co.vividnext.sodalive.v2.ranking.application import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingPeriodPolicy import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScorePolicy import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate +import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingUtcRange import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult @@ -29,6 +30,7 @@ class CreatorRankingSnapshotRefreshService( val startedAt = System.currentTimeMillis() val period = periodPolicy.resolveLastCompletedWeek(now) val utcRange = periodPolicy.toUtcRange(period) + val visibleFromAtUtc = periodPolicy.resolveVisibleFromAtUtc(period.endExclusiveKst) runCatching { val aggregationResult = aggregationPort.aggregateCandidateResult( startInclusiveUtc = utcRange.startInclusiveUtc, @@ -39,8 +41,10 @@ class CreatorRankingSnapshotRefreshService( .takeRankedBoundary(limit = SNAPSHOT_LIMIT) snapshotPort.replaceSnapshots( + rankingType = CreatorRankingType.WEEKLY, aggregationStartAtUtc = utcRange.startInclusiveUtc, aggregationEndAtUtc = utcRange.endExclusiveUtc, + visibleFromAtUtc = visibleFromAtUtc, newSnapshots = snapshots ) aggregationResult.toLogCounts(storedCount = snapshots.size) @@ -124,8 +128,10 @@ class CreatorRankingSnapshotRefreshService( ) return CreatorRankingSnapshotRecord( + rankingType = CreatorRankingType.WEEKLY, aggregationStartAtUtc = utcRange.startInclusiveUtc, aggregationEndAtUtc = utcRange.endExclusiveUtc, + visibleFromAtUtc = utcRange.endExclusiveUtc.plusHours(9), creatorId = creatorId, nickname = nickname, profileImageUrl = profileImageUrl, diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt index f43fa67e..8a7fa528 100644 --- a/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/ranking/application/CreatorRankingSnapshotRefreshServiceTest.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.v2.ranking.application import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate +import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotPort @@ -49,6 +50,8 @@ class CreatorRankingSnapshotRefreshServiceTest { assertEquals(LocalDateTime.of(2026, 6, 7, 15, 0, 0), aggregationPort.endExclusiveUtc) assertEquals(aggregationPort.startInclusiveUtc, snapshotPort.aggregationStartAtUtc) assertEquals(aggregationPort.endExclusiveUtc, snapshotPort.aggregationEndAtUtc) + assertEquals(CreatorRankingType.WEEKLY, snapshotPort.rankingType) + assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), snapshotPort.visibleFromAtUtc) assertEquals(85.0, stored.contentLiveScore, 0.0001) assertEquals(7.0, stored.engagementScore, 0.0001) assertEquals(19.8, stored.supportScore, 0.0001) @@ -240,8 +243,10 @@ private class FakeCreatorRankingAggregationPort : CreatorRankingAggregationPort private class FakeCreatorRankingSnapshotPort : CreatorRankingSnapshotPort { val snapshots = mutableListOf() + var rankingType: CreatorRankingType? = null var aggregationStartAtUtc: LocalDateTime? = null var aggregationEndAtUtc: LocalDateTime? = null + var visibleFromAtUtc: LocalDateTime? = null override fun findSnapshotsByAggregationPeriod( aggregationStartAtUtc: LocalDateTime, @@ -256,15 +261,30 @@ private class FakeCreatorRankingSnapshotPort : CreatorRankingSnapshotPort { override fun findPreviousCompletedSnapshots(): List = snapshots + override fun findLatestVisibleSnapshots( + rankingType: CreatorRankingType, + nowUtc: LocalDateTime + ): List = snapshots + + override fun findPreviousVisibleSnapshots( + rankingType: CreatorRankingType, + currentAggregationStartAtUtc: LocalDateTime, + nowUtc: LocalDateTime + ): List = snapshots + override fun isSnapshotTableEmpty(): Boolean = snapshots.isEmpty() override fun replaceSnapshots( + rankingType: CreatorRankingType, aggregationStartAtUtc: LocalDateTime, aggregationEndAtUtc: LocalDateTime, + visibleFromAtUtc: LocalDateTime, newSnapshots: List ) { + this.rankingType = rankingType this.aggregationStartAtUtc = aggregationStartAtUtc this.aggregationEndAtUtc = aggregationEndAtUtc + this.visibleFromAtUtc = visibleFromAtUtc snapshots.removeIf { it.aggregationStartAtUtc == aggregationStartAtUtc && it.aggregationEndAtUtc == aggregationEndAtUtc }