test #426
@@ -0,0 +1,201 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingPeriod
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingPeriodPolicy
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSchedulePolicy
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingScorePolicy
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSnapshotCandidate
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingUtcRange
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingAggregationPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class AudioRankingSnapshotRefreshService(
|
||||||
|
private val aggregationPort: AudioRankingAggregationPort,
|
||||||
|
private val snapshotPort: AudioRankingSnapshotPort
|
||||||
|
) {
|
||||||
|
private val periodPolicy = AudioRankingPeriodPolicy()
|
||||||
|
private val schedulePolicy = AudioRankingSchedulePolicy()
|
||||||
|
private val scorePolicy = AudioRankingScorePolicy()
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun refreshLastCompletedWeek(type: AudioRankingType, now: ZonedDateTime) {
|
||||||
|
val period = periodPolicy.resolveLastCompletedWeek(now)
|
||||||
|
val utcRange = periodPolicy.toUtcRange(period)
|
||||||
|
val visibleFromAtUtc = schedulePolicy.resolveVisibleFromAt(period.endExclusiveKst)
|
||||||
|
val candidates = resolveCandidates(type, utcRange)
|
||||||
|
val snapshots = candidates.toSnapshotRecords(type, period, utcRange, visibleFromAtUtc)
|
||||||
|
|
||||||
|
snapshotPort.replaceSnapshots(
|
||||||
|
rankingType = type,
|
||||||
|
aggregationStartAtUtc = utcRange.startInclusiveUtc,
|
||||||
|
aggregationEndAtUtc = utcRange.endExclusiveUtc,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
newSnapshots = snapshots
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveCandidates(
|
||||||
|
type: AudioRankingType,
|
||||||
|
utcRange: AudioRankingUtcRange
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
return when (type) {
|
||||||
|
AudioRankingType.WEEKLY_POPULAR -> aggregationPort.aggregateWeeklyPopularCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
AudioRankingType.RISING -> aggregationPort.aggregateRisingCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
AudioRankingType.REVENUE -> aggregationPort.aggregateRevenueCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
AudioRankingType.SALES_COUNT -> aggregationPort.aggregateSalesCountCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
AudioRankingType.COMMENT_COUNT -> aggregationPort.aggregateCommentCountCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
AudioRankingType.LIKE_COUNT -> aggregationPort.aggregateLikeCountCandidates(
|
||||||
|
utcRange.startInclusiveUtc,
|
||||||
|
utcRange.endExclusiveUtc
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<AudioRankingSnapshotCandidate>.toSnapshotRecords(
|
||||||
|
type: AudioRankingType,
|
||||||
|
period: AudioRankingPeriod,
|
||||||
|
utcRange: AudioRankingUtcRange,
|
||||||
|
visibleFromAtUtc: java.time.LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotRecord> {
|
||||||
|
val scoredCandidates = when (type) {
|
||||||
|
AudioRankingType.WEEKLY_POPULAR -> withWeeklyPopularScores()
|
||||||
|
AudioRankingType.RISING -> withRisingScores(period)
|
||||||
|
AudioRankingType.REVENUE,
|
||||||
|
AudioRankingType.SALES_COUNT,
|
||||||
|
AudioRankingType.COMMENT_COUNT,
|
||||||
|
AudioRankingType.LIKE_COUNT -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
val rankedRecords = scoredCandidates
|
||||||
|
.sortedWith(
|
||||||
|
compareByDescending<AudioRankingSnapshotCandidate> { it.finalScore }
|
||||||
|
.thenByDescending { it.releaseDate }
|
||||||
|
.thenByDescending { it.contentId }
|
||||||
|
)
|
||||||
|
.mapIndexed { index, candidate -> candidate.toSnapshotRecord(type, utcRange, visibleFromAtUtc, index + 1) }
|
||||||
|
|
||||||
|
val globalContentIds = rankedRecords.take(SNAPSHOT_LIMIT).map { it.contentId }.toSet()
|
||||||
|
val safeContentIds = rankedRecords.filter { !it.isAdult }.take(SNAPSHOT_LIMIT).map { it.contentId }.toSet()
|
||||||
|
val selectedContentIds = globalContentIds + safeContentIds
|
||||||
|
|
||||||
|
return rankedRecords.filter { it.contentId in selectedContentIds }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<AudioRankingSnapshotCandidate>.withWeeklyPopularScores(): List<AudioRankingSnapshotCandidate> {
|
||||||
|
val rawScores = associateWith { candidate ->
|
||||||
|
scorePolicy.calculateWeeklyPopularScore(
|
||||||
|
revenue = candidate.revenueCanAmount,
|
||||||
|
salesCount = candidate.salesCount,
|
||||||
|
viewCount = candidate.viewCount,
|
||||||
|
likeCount = candidate.likeCount,
|
||||||
|
commentCount = candidate.commentCount,
|
||||||
|
isPaid = candidate.isPaid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val paidMaxScore = rawScores.filterKeys { it.isPaid }.values.maxOrNull() ?: 0.0
|
||||||
|
val freeMaxScore = rawScores.filterKeys { !it.isPaid }.values.maxOrNull() ?: 0.0
|
||||||
|
|
||||||
|
return map { candidate ->
|
||||||
|
val rawScore = rawScores.getValue(candidate)
|
||||||
|
candidate.copy(
|
||||||
|
finalScore = scorePolicy.normalizeScore(
|
||||||
|
rawScore,
|
||||||
|
if (candidate.isPaid) paidMaxScore else freeMaxScore
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AudioRankingSnapshotCandidate.withRisingScore(period: AudioRankingPeriod): AudioRankingSnapshotCandidate {
|
||||||
|
return copy(
|
||||||
|
finalScore = scorePolicy.calculateRisingScore(
|
||||||
|
recentSalesCount = salesCount,
|
||||||
|
previousSalesCount = previousSalesCount,
|
||||||
|
recentViewCount = viewCount,
|
||||||
|
previousViewCount = previousViewCount,
|
||||||
|
recentLikeCount = likeCount,
|
||||||
|
previousLikeCount = previousLikeCount,
|
||||||
|
recentCommentCount = commentCount,
|
||||||
|
previousCommentCount = previousCommentCount,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
aggregationEndAt = period.endExclusiveKst,
|
||||||
|
isPaid = isPaid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<AudioRankingSnapshotCandidate>.withRisingScores(
|
||||||
|
period: AudioRankingPeriod
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
val scoredCandidates = map { it.withRisingScore(period) }
|
||||||
|
val paidMaxScore = scoredCandidates.filter { it.isPaid }.maxOfOrNull { it.finalScore } ?: 0.0
|
||||||
|
val freeMaxScore = scoredCandidates.filter { !it.isPaid }.maxOfOrNull { it.finalScore } ?: 0.0
|
||||||
|
|
||||||
|
return scoredCandidates.map { candidate ->
|
||||||
|
candidate.copy(
|
||||||
|
finalScore = scorePolicy.normalizeScore(
|
||||||
|
candidate.finalScore,
|
||||||
|
if (candidate.isPaid) paidMaxScore else freeMaxScore
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AudioRankingSnapshotCandidate.toSnapshotRecord(
|
||||||
|
type: AudioRankingType,
|
||||||
|
utcRange: AudioRankingUtcRange,
|
||||||
|
visibleFromAtUtc: java.time.LocalDateTime,
|
||||||
|
rank: Int
|
||||||
|
): AudioRankingSnapshotRecord {
|
||||||
|
return AudioRankingSnapshotRecord(
|
||||||
|
rankingType = type,
|
||||||
|
aggregationStartAtUtc = utcRange.startInclusiveUtc,
|
||||||
|
aggregationEndAtUtc = utcRange.endExclusiveUtc,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
contentId = contentId,
|
||||||
|
title = title,
|
||||||
|
creatorMemberId = creatorMemberId,
|
||||||
|
creatorNickname = creatorNickname,
|
||||||
|
coverImageUrl = coverImageUrl,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
isAdult = isAdult,
|
||||||
|
rank = rank,
|
||||||
|
finalScore = max(finalScore, 0.0),
|
||||||
|
revenueCanAmount = revenueCanAmount,
|
||||||
|
salesCount = salesCount,
|
||||||
|
viewCount = viewCount,
|
||||||
|
likeCount = likeCount,
|
||||||
|
commentCount = commentCount,
|
||||||
|
previousSalesCount = previousSalesCount,
|
||||||
|
previousViewCount = previousViewCount,
|
||||||
|
previousLikeCount = previousLikeCount,
|
||||||
|
previousCommentCount = previousCommentCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SNAPSHOT_LIMIT = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.content.ranking.application
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingSnapshotCandidate
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.domain.AudioRankingType
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingAggregationPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotPort
|
||||||
|
import kr.co.vividnext.sodalive.v2.content.ranking.port.out.AudioRankingSnapshotRecord
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
class AudioRankingSnapshotRefreshServiceTest {
|
||||||
|
@Test
|
||||||
|
fun shouldStoreTopTwentyByScoreReleaseDateAndContentId() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.weeklyCandidates = (1L..18L).map { contentId ->
|
||||||
|
candidate(contentId = contentId, salesCount = 100 - contentId, releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0))
|
||||||
|
} + listOf(
|
||||||
|
candidate(contentId = 19L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0)),
|
||||||
|
candidate(contentId = 20L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 3, 0, 0)),
|
||||||
|
candidate(contentId = 21L, salesCount = 10, releaseDate = LocalDateTime.of(2026, 6, 3, 0, 0)),
|
||||||
|
candidate(contentId = 22L, salesCount = 1, releaseDate = LocalDateTime.of(2026, 6, 4, 0, 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.WEEKLY_POPULAR, now())
|
||||||
|
|
||||||
|
assertEquals(20, snapshotPort.snapshots.size)
|
||||||
|
assertEquals(listOf(21L, 20L), snapshotPort.snapshots.takeLast(2).map { it.contentId })
|
||||||
|
assertEquals((1..20).toList(), snapshotPort.snapshots.map { it.rank })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldUseLastCompletedWeekUtcRangeAndVisibleFromAt() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.risingCandidates = listOf(candidate(contentId = 1L, viewCount = 20, previousViewCount = 10))
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.RISING, now())
|
||||||
|
|
||||||
|
assertEquals(LocalDateTime.of(2026, 5, 31, 15, 0), aggregationPort.startInclusiveUtc)
|
||||||
|
assertEquals(LocalDateTime.of(2026, 6, 7, 15, 0), aggregationPort.endExclusiveUtc)
|
||||||
|
assertEquals(AudioRankingType.RISING, snapshotPort.rankingType)
|
||||||
|
assertEquals(LocalDateTime.of(2026, 5, 31, 15, 0), snapshotPort.aggregationStartAtUtc)
|
||||||
|
assertEquals(LocalDateTime.of(2026, 6, 7, 15, 0), snapshotPort.aggregationEndAtUtc)
|
||||||
|
assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), snapshotPort.visibleFromAtUtc)
|
||||||
|
assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), snapshotPort.snapshots.single().visibleFromAtUtc)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldNormalizeRisingScoresByPaidAndFreeGroups() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.risingCandidates = listOf(
|
||||||
|
candidate(contentId = 1L, salesCount = 6, previousSalesCount = 3),
|
||||||
|
candidate(contentId = 2L, salesCount = 3, previousSalesCount = 3),
|
||||||
|
candidate(contentId = 3L, viewCount = 20, previousViewCount = 10),
|
||||||
|
candidate(contentId = 4L, viewCount = 10, previousViewCount = 10)
|
||||||
|
)
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.RISING, now())
|
||||||
|
|
||||||
|
val scoresByContentId = snapshotPort.snapshots.associate { it.contentId to it.finalScore }
|
||||||
|
assertEquals(100.0, scoresByContentId.getValue(1L), 0.0001)
|
||||||
|
assertEquals(0.0, scoresByContentId.getValue(2L), 0.0001)
|
||||||
|
assertEquals(100.0, scoresByContentId.getValue(3L), 0.0001)
|
||||||
|
assertEquals(0.0, scoresByContentId.getValue(4L), 0.0001)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldUseAggregationPortForMetricRankingTypes() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.revenueCandidates = listOf(candidate(contentId = 1L, finalScore = 10.0))
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||||
|
|
||||||
|
assertEquals(AudioRankingType.REVENUE, aggregationPort.metricRankingType)
|
||||||
|
assertEquals(listOf(1L), snapshotPort.snapshots.map { it.contentId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldSortMetricTieScoresByReleaseDateAndContentId() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.revenueCandidates = listOf(
|
||||||
|
candidate(contentId = 1L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 1, 0, 0)),
|
||||||
|
candidate(contentId = 2L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0)),
|
||||||
|
candidate(contentId = 3L, finalScore = 10.0, releaseDate = LocalDateTime.of(2026, 6, 2, 0, 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||||
|
|
||||||
|
assertEquals(listOf(3L, 2L, 1L), snapshotPort.snapshots.map { it.contentId })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shouldStoreGlobalTopTwentyAndSafeTopTwentyCandidates() {
|
||||||
|
val aggregationPort = FakeAudioRankingAggregationPort()
|
||||||
|
val snapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
val service = service(aggregationPort = aggregationPort, snapshotPort = snapshotPort)
|
||||||
|
aggregationPort.revenueCandidates = (1L..20L).map { contentId ->
|
||||||
|
candidate(contentId = contentId, finalScore = (100 - contentId).toDouble(), isAdult = true)
|
||||||
|
} + (21L..40L).map { contentId ->
|
||||||
|
candidate(contentId = contentId, finalScore = (100 - contentId).toDouble(), isAdult = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
service.refreshLastCompletedWeek(AudioRankingType.REVENUE, now())
|
||||||
|
|
||||||
|
assertEquals(40, snapshotPort.snapshots.size)
|
||||||
|
assertEquals((1L..20L).toList(), snapshotPort.snapshots.take(20).map { it.contentId })
|
||||||
|
assertEquals((21L..40L).toList(), snapshotPort.snapshots.drop(20).map { it.contentId })
|
||||||
|
assertEquals((1..40).toList(), snapshotPort.snapshots.map { it.rank })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun service(
|
||||||
|
aggregationPort: AudioRankingAggregationPort = FakeAudioRankingAggregationPort(),
|
||||||
|
snapshotPort: AudioRankingSnapshotPort = FakeAudioRankingSnapshotPort()
|
||||||
|
): AudioRankingSnapshotRefreshService {
|
||||||
|
return AudioRankingSnapshotRefreshService(
|
||||||
|
aggregationPort = aggregationPort,
|
||||||
|
snapshotPort = snapshotPort
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun now(): ZonedDateTime {
|
||||||
|
return ZonedDateTime.of(2026, 6, 8, 6, 0, 0, 0, ZoneId.of("Asia/Seoul"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun candidate(
|
||||||
|
contentId: Long,
|
||||||
|
finalScore: Double = 0.0,
|
||||||
|
salesCount: Long = 0,
|
||||||
|
previousSalesCount: Long = 0,
|
||||||
|
viewCount: Long = 0,
|
||||||
|
previousViewCount: Long = 0,
|
||||||
|
releaseDate: LocalDateTime = LocalDateTime.of(2026, 6, 1, 0, 0),
|
||||||
|
isAdult: Boolean = false
|
||||||
|
): AudioRankingSnapshotCandidate {
|
||||||
|
return AudioRankingSnapshotCandidate(
|
||||||
|
contentId = contentId,
|
||||||
|
title = "audio-$contentId",
|
||||||
|
creatorMemberId = 100L + contentId,
|
||||||
|
creatorNickname = "creator-$contentId",
|
||||||
|
coverImageUrl = "cover-$contentId.png",
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
isAdult = isAdult,
|
||||||
|
isPaid = salesCount > 0,
|
||||||
|
finalScore = finalScore,
|
||||||
|
salesCount = salesCount,
|
||||||
|
previousSalesCount = previousSalesCount,
|
||||||
|
viewCount = viewCount,
|
||||||
|
previousViewCount = previousViewCount
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeAudioRankingAggregationPort : AudioRankingAggregationPort {
|
||||||
|
var weeklyCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var risingCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var revenueCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var salesCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var commentCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var likeCountCandidates: List<AudioRankingSnapshotCandidate> = emptyList()
|
||||||
|
var startInclusiveUtc: LocalDateTime? = null
|
||||||
|
var endExclusiveUtc: LocalDateTime? = null
|
||||||
|
var metricRankingType: AudioRankingType? = null
|
||||||
|
|
||||||
|
override fun aggregateWeeklyPopularCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
return weeklyCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun aggregateRisingCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
return risingCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun aggregateRevenueCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
metricRankingType = AudioRankingType.REVENUE
|
||||||
|
return revenueCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun aggregateSalesCountCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
metricRankingType = AudioRankingType.SALES_COUNT
|
||||||
|
return salesCountCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun aggregateCommentCountCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
metricRankingType = AudioRankingType.COMMENT_COUNT
|
||||||
|
return commentCountCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun aggregateLikeCountCandidates(
|
||||||
|
startInclusiveUtc: LocalDateTime,
|
||||||
|
endExclusiveUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotCandidate> {
|
||||||
|
this.startInclusiveUtc = startInclusiveUtc
|
||||||
|
this.endExclusiveUtc = endExclusiveUtc
|
||||||
|
metricRankingType = AudioRankingType.LIKE_COUNT
|
||||||
|
return likeCountCandidates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeAudioRankingSnapshotPort : AudioRankingSnapshotPort {
|
||||||
|
val snapshots = mutableListOf<AudioRankingSnapshotRecord>()
|
||||||
|
var rankingType: AudioRankingType? = null
|
||||||
|
var aggregationStartAtUtc: LocalDateTime? = null
|
||||||
|
var aggregationEndAtUtc: LocalDateTime? = null
|
||||||
|
var visibleFromAtUtc: LocalDateTime? = null
|
||||||
|
|
||||||
|
override fun findLatestVisibleSnapshots(
|
||||||
|
rankingType: AudioRankingType,
|
||||||
|
nowUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotRecord> = snapshots
|
||||||
|
|
||||||
|
override fun findPreviousVisibleSnapshots(
|
||||||
|
rankingType: AudioRankingType,
|
||||||
|
currentAggregationStartAtUtc: LocalDateTime,
|
||||||
|
nowUtc: LocalDateTime
|
||||||
|
): List<AudioRankingSnapshotRecord> = snapshots
|
||||||
|
|
||||||
|
override fun replaceSnapshots(
|
||||||
|
rankingType: AudioRankingType,
|
||||||
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
|
aggregationEndAtUtc: LocalDateTime,
|
||||||
|
visibleFromAtUtc: LocalDateTime,
|
||||||
|
newSnapshots: List<AudioRankingSnapshotRecord>
|
||||||
|
) {
|
||||||
|
this.rankingType = rankingType
|
||||||
|
this.aggregationStartAtUtc = aggregationStartAtUtc
|
||||||
|
this.aggregationEndAtUtc = aggregationEndAtUtc
|
||||||
|
this.visibleFromAtUtc = visibleFromAtUtc
|
||||||
|
snapshots.clear()
|
||||||
|
snapshots.addAll(newSnapshots)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user