feat(home): 크리에이터 랭킹 스냅샷 저장 구조를 변경한다
This commit is contained in:
@@ -34,45 +34,15 @@ class CreatorRankingSnapshot(
|
|||||||
@Column(name = "profile_image_url", updatable = false, length = 500)
|
@Column(name = "profile_image_url", updatable = false, length = 500)
|
||||||
val profileImageUrl: String?,
|
val profileImageUrl: String?,
|
||||||
|
|
||||||
|
@Column(name = "rank_no", nullable = false, updatable = false)
|
||||||
|
val rankNo: Int,
|
||||||
|
|
||||||
@Column(name = "final_score", nullable = false, updatable = false)
|
@Column(name = "final_score", nullable = false, updatable = false)
|
||||||
val finalScore: Double,
|
val finalScore: Double,
|
||||||
|
|
||||||
@Column(name = "content_live_score", nullable = false, updatable = false)
|
@Column(name = "score_policy_version", nullable = false, updatable = false, length = 80)
|
||||||
val contentLiveScore: Double,
|
val scorePolicyVersion: String,
|
||||||
|
|
||||||
@Column(name = "engagement_score", nullable = false, updatable = false)
|
@Column(name = "score_detail_json", nullable = false, updatable = false, columnDefinition = "json")
|
||||||
val engagementScore: Double,
|
val scoreDetailJson: String
|
||||||
|
|
||||||
@Column(name = "support_score", nullable = false, updatable = false)
|
|
||||||
val supportScore: Double,
|
|
||||||
|
|
||||||
@Column(name = "fan_loyalty_score", nullable = false, updatable = false)
|
|
||||||
val fanLoyaltyScore: Double,
|
|
||||||
|
|
||||||
@Column(name = "live_can_amount", nullable = false, updatable = false)
|
|
||||||
val liveCanAmount: Long,
|
|
||||||
|
|
||||||
@Column(name = "content_purchase_can_amount", nullable = false, updatable = false)
|
|
||||||
val contentPurchaseCanAmount: Long,
|
|
||||||
|
|
||||||
@Column(name = "content_like_count", nullable = false, updatable = false)
|
|
||||||
val contentLikeCount: Long,
|
|
||||||
|
|
||||||
@Column(name = "content_comment_count", nullable = false, updatable = false)
|
|
||||||
val contentCommentCount: Long,
|
|
||||||
|
|
||||||
@Column(name = "channel_donation_can_amount", nullable = false, updatable = false)
|
|
||||||
val channelDonationCanAmount: Long,
|
|
||||||
|
|
||||||
@Column(name = "channel_donation_count", nullable = false, updatable = false)
|
|
||||||
val channelDonationCount: Long,
|
|
||||||
|
|
||||||
@Column(name = "fan_talk_count", nullable = false, updatable = false)
|
|
||||||
val fanTalkCount: Long,
|
|
||||||
|
|
||||||
@Column(name = "final_follower_count", nullable = false, updatable = false)
|
|
||||||
val finalFollowerCount: Long,
|
|
||||||
|
|
||||||
@Column(name = "follow_increase", nullable = false, updatable = false)
|
|
||||||
val followIncrease: Long
|
|
||||||
) : BaseEntity()
|
) : BaseEntity()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.springframework.data.repository.query.Param
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapshot, Long> {
|
interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapshot, Long> {
|
||||||
fun findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
|
fun findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByRankNoAsc(
|
||||||
aggregationStartAtUtc: LocalDateTime,
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
aggregationEndAtUtc: LocalDateTime
|
aggregationEndAtUtc: LocalDateTime
|
||||||
): List<CreatorRankingSnapshot>
|
): List<CreatorRankingSnapshot>
|
||||||
@@ -20,7 +20,7 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
|
|||||||
select max(latest.aggregation_end_at_utc)
|
select max(latest.aggregation_end_at_utc)
|
||||||
from creator_ranking_snapshot latest
|
from creator_ranking_snapshot latest
|
||||||
)
|
)
|
||||||
order by crs.final_score desc
|
order by crs.rank_no asc
|
||||||
""",
|
""",
|
||||||
nativeQuery = true
|
nativeQuery = true
|
||||||
)
|
)
|
||||||
@@ -38,7 +38,7 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
|
|||||||
from creator_ranking_snapshot latest
|
from creator_ranking_snapshot latest
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
order by crs.final_score desc
|
order by crs.rank_no asc
|
||||||
""",
|
""",
|
||||||
nativeQuery = true
|
nativeQuery = true
|
||||||
)
|
)
|
||||||
@@ -55,7 +55,7 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
|
|||||||
where latest.ranking_type = :rankingType
|
where latest.ranking_type = :rankingType
|
||||||
and latest.visible_from_at <= :nowUtc
|
and latest.visible_from_at <= :nowUtc
|
||||||
)
|
)
|
||||||
order by crs.final_score desc
|
order by crs.rank_no asc
|
||||||
""",
|
""",
|
||||||
nativeQuery = true
|
nativeQuery = true
|
||||||
)
|
)
|
||||||
@@ -69,6 +69,7 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
|
|||||||
select *
|
select *
|
||||||
from creator_ranking_snapshot crs
|
from creator_ranking_snapshot crs
|
||||||
where crs.ranking_type = :rankingType
|
where crs.ranking_type = :rankingType
|
||||||
|
and crs.visible_from_at <= :nowUtc
|
||||||
and crs.aggregation_start_at_utc = (
|
and crs.aggregation_start_at_utc = (
|
||||||
select max(previous.aggregation_start_at_utc)
|
select max(previous.aggregation_start_at_utc)
|
||||||
from creator_ranking_snapshot previous
|
from creator_ranking_snapshot previous
|
||||||
@@ -76,7 +77,7 @@ interface CreatorRankingSnapshotRepository : JpaRepository<CreatorRankingSnapsho
|
|||||||
and previous.aggregation_start_at_utc < :currentAggregationStartAtUtc
|
and previous.aggregation_start_at_utc < :currentAggregationStartAtUtc
|
||||||
and previous.visible_from_at <= :nowUtc
|
and previous.visible_from_at <= :nowUtc
|
||||||
)
|
)
|
||||||
order by crs.final_score desc
|
order by crs.rank_no asc
|
||||||
""",
|
""",
|
||||||
nativeQuery = true
|
nativeQuery = true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
aggregationStartAtUtc: LocalDateTime,
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
aggregationEndAtUtc: LocalDateTime
|
aggregationEndAtUtc: LocalDateTime
|
||||||
): List<CreatorRankingSnapshotRecord> {
|
): List<CreatorRankingSnapshotRecord> {
|
||||||
return repository.findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByFinalScoreDesc(
|
return repository.findAllByAggregationStartAtUtcAndAggregationEndAtUtcOrderByRankNoAsc(
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc
|
aggregationEndAtUtc = aggregationEndAtUtc
|
||||||
).map { it.toRecord() }
|
).map { it.toRecord() }
|
||||||
@@ -42,16 +42,12 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
nowUtc: LocalDateTime
|
nowUtc: LocalDateTime
|
||||||
): List<CreatorRankingSnapshotRecord> {
|
): List<CreatorRankingSnapshotRecord> {
|
||||||
return repository.findPreviousVisibleSnapshots(
|
return repository.findPreviousVisibleSnapshots(
|
||||||
rankingType = rankingType.name,
|
rankingType.name,
|
||||||
currentAggregationStartAtUtc = currentAggregationStartAtUtc,
|
currentAggregationStartAtUtc,
|
||||||
nowUtc = nowUtc
|
nowUtc
|
||||||
).map { it.toRecord() }
|
).map { it.toRecord() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isSnapshotTableEmpty(): Boolean {
|
|
||||||
return repository.count() == 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun replaceSnapshots(
|
override fun replaceSnapshots(
|
||||||
rankingType: CreatorRankingType,
|
rankingType: CreatorRankingType,
|
||||||
@@ -61,9 +57,9 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
newSnapshots: List<CreatorRankingSnapshotRecord>
|
newSnapshots: List<CreatorRankingSnapshotRecord>
|
||||||
) {
|
) {
|
||||||
repository.deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
|
repository.deleteByRankingTypeAndAggregationStartAtUtcAndAggregationEndAtUtc(
|
||||||
rankingType = rankingType,
|
rankingType,
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
aggregationStartAtUtc,
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc
|
aggregationEndAtUtc
|
||||||
)
|
)
|
||||||
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
|
repository.saveAll(newSnapshots.map { it.toEntity(rankingType, visibleFromAtUtc) })
|
||||||
}
|
}
|
||||||
@@ -77,20 +73,10 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = nickname,
|
nickname = nickname,
|
||||||
profileImageUrl = profileImageUrl,
|
profileImageUrl = profileImageUrl,
|
||||||
|
rankNo = rankNo,
|
||||||
finalScore = finalScore,
|
finalScore = finalScore,
|
||||||
contentLiveScore = contentLiveScore,
|
scorePolicyVersion = scorePolicyVersion,
|
||||||
engagementScore = engagementScore,
|
scoreDetailJson = scoreDetailJson
|
||||||
supportScore = supportScore,
|
|
||||||
fanLoyaltyScore = fanLoyaltyScore,
|
|
||||||
liveCanAmount = liveCanAmount,
|
|
||||||
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
|
||||||
contentLikeCount = contentLikeCount,
|
|
||||||
contentCommentCount = contentCommentCount,
|
|
||||||
channelDonationCanAmount = channelDonationCanAmount,
|
|
||||||
channelDonationCount = channelDonationCount,
|
|
||||||
fanTalkCount = fanTalkCount,
|
|
||||||
finalFollowerCount = finalFollowerCount,
|
|
||||||
followIncrease = followIncrease
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,20 +92,10 @@ class DefaultCreatorRankingSnapshotRepository(
|
|||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = nickname,
|
nickname = nickname,
|
||||||
profileImageUrl = profileImageUrl,
|
profileImageUrl = profileImageUrl,
|
||||||
|
rankNo = rankNo,
|
||||||
finalScore = finalScore,
|
finalScore = finalScore,
|
||||||
contentLiveScore = contentLiveScore,
|
scorePolicyVersion = scorePolicyVersion,
|
||||||
engagementScore = engagementScore,
|
scoreDetailJson = scoreDetailJson
|
||||||
supportScore = supportScore,
|
|
||||||
fanLoyaltyScore = fanLoyaltyScore,
|
|
||||||
liveCanAmount = liveCanAmount,
|
|
||||||
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
|
||||||
contentLikeCount = contentLikeCount,
|
|
||||||
contentCommentCount = contentCommentCount,
|
|
||||||
channelDonationCanAmount = channelDonationCanAmount,
|
|
||||||
channelDonationCount = channelDonationCount,
|
|
||||||
fanTalkCount = fanTalkCount,
|
|
||||||
finalFollowerCount = finalFollowerCount,
|
|
||||||
followIncrease = followIncrease
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ interface CreatorRankingSnapshotPort {
|
|||||||
nowUtc: LocalDateTime
|
nowUtc: LocalDateTime
|
||||||
): List<CreatorRankingSnapshotRecord>
|
): List<CreatorRankingSnapshotRecord>
|
||||||
|
|
||||||
fun isSnapshotTableEmpty(): Boolean
|
|
||||||
|
|
||||||
fun replaceSnapshots(
|
fun replaceSnapshots(
|
||||||
rankingType: CreatorRankingType,
|
rankingType: CreatorRankingType,
|
||||||
aggregationStartAtUtc: LocalDateTime,
|
aggregationStartAtUtc: LocalDateTime,
|
||||||
@@ -43,18 +41,8 @@ data class CreatorRankingSnapshotRecord(
|
|||||||
val creatorId: Long,
|
val creatorId: Long,
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
val profileImageUrl: String?,
|
val profileImageUrl: String?,
|
||||||
|
val rankNo: Int,
|
||||||
val finalScore: Double,
|
val finalScore: Double,
|
||||||
val contentLiveScore: Double,
|
val scorePolicyVersion: String,
|
||||||
val engagementScore: Double,
|
val scoreDetailJson: String
|
||||||
val supportScore: Double,
|
|
||||||
val fanLoyaltyScore: Double,
|
|
||||||
val liveCanAmount: Long,
|
|
||||||
val contentPurchaseCanAmount: Long,
|
|
||||||
val contentLikeCount: Long,
|
|
||||||
val contentCommentCount: Long,
|
|
||||||
val channelDonationCanAmount: Long,
|
|
||||||
val channelDonationCount: Long,
|
|
||||||
val fanTalkCount: Long,
|
|
||||||
val finalFollowerCount: Long,
|
|
||||||
val followIncrease: Long
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
import kr.co.vividnext.sodalive.configs.QueryDslConfig
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingScoreSpec
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingType
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingSnapshotRecord
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
@@ -14,345 +15,315 @@ import java.time.LocalDateTime
|
|||||||
@DataJpaTest(
|
@DataJpaTest(
|
||||||
properties = [
|
properties = [
|
||||||
"spring.cache.type=none",
|
"spring.cache.type=none",
|
||||||
"spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;NON_KEYWORDS=VALUE"
|
"spring.datasource.url=jdbc:h2:mem:creator-ranking-snapshot;" +
|
||||||
|
"MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@Import(QueryDslConfig::class)
|
@Import(QueryDslConfig::class, DefaultCreatorRankingSnapshotRepository::class)
|
||||||
class DefaultCreatorRankingSnapshotRepositoryTest @Autowired constructor(
|
class DefaultCreatorRankingSnapshotRepositoryTest @Autowired constructor(
|
||||||
|
private val adapter: DefaultCreatorRankingSnapshotRepository,
|
||||||
private val repository: CreatorRankingSnapshotRepository
|
private val repository: CreatorRankingSnapshotRepository
|
||||||
) {
|
) {
|
||||||
private val adapter = DefaultCreatorRankingSnapshotRepository(repository)
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("같은 집계 기간의 스냅샷은 삭제 후 새 후보로 교체한다")
|
@DisplayName("replaceSnapshots는 rankNo, finalScore, scorePolicyVersion, scoreDetailJson을 저장하고 매핑한다")
|
||||||
fun shouldReplaceSnapshotsByAggregationPeriod() {
|
fun shouldReplaceSnapshotsAndMapV2SnapshotContractFields() {
|
||||||
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
val oldStartAt = startAt.minusWeeks(1)
|
|
||||||
val oldEndAt = endAt.minusWeeks(1)
|
|
||||||
repository.saveAll(
|
|
||||||
listOf(
|
|
||||||
snapshot(creatorId = 1L, aggregationStartAtUtc = oldStartAt, aggregationEndAtUtc = oldEndAt),
|
|
||||||
snapshot(creatorId = 2L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
adapter.replaceSnapshots(
|
adapter.replaceSnapshots(
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
rankingType = CreatorRankingType.WEEKLY,
|
||||||
aggregationStartAtUtc = startAt,
|
aggregationStartAtUtc = START,
|
||||||
aggregationEndAtUtc = endAt,
|
aggregationEndAtUtc = END,
|
||||||
visibleFromAtUtc = endAt.plusHours(9),
|
visibleFromAtUtc = VISIBLE,
|
||||||
newSnapshots = listOf(snapshotRecord(creatorId = 3L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt))
|
newSnapshots = listOf(
|
||||||
|
record(
|
||||||
|
creatorId = 1L,
|
||||||
|
rankNo = 2,
|
||||||
|
finalScore = 88.5,
|
||||||
|
scorePolicyVersion = CreatorRankingScoreSpec.SCORE_POLICY_VERSION,
|
||||||
|
scoreDetailJson = "{\"liveCanAmount\":10}"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val allSnapshots = repository.findAll()
|
|
||||||
assertEquals(listOf(1L, 3L), allSnapshots.map { it.creatorId }.sorted())
|
|
||||||
assertEquals(listOf(3L), adapter.findLatestSnapshots().map { it.creatorId })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("스냅샷은 랭킹 타입과 공개 노출 시각을 저장하고 같은 타입/기간만 교체한다")
|
|
||||||
fun shouldPersistRankingTypeAndVisibleFromAtAndReplaceByTypeAndPeriod() {
|
|
||||||
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
val visibleFromAt = LocalDateTime.of(2026, 6, 8, 0, 0)
|
|
||||||
repository.save(snapshot(creatorId = 1L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt))
|
|
||||||
|
|
||||||
adapter.replaceSnapshots(
|
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
|
||||||
aggregationStartAtUtc = startAt,
|
|
||||||
aggregationEndAtUtc = endAt,
|
|
||||||
visibleFromAtUtc = visibleFromAt,
|
|
||||||
newSnapshots = listOf(snapshotRecord(creatorId = 2L, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val saved = repository.findAll().single()
|
val saved = repository.findAll().single()
|
||||||
assertEquals(CreatorRankingType.WEEKLY, saved.rankingType)
|
assertEquals(2, saved.rankNo)
|
||||||
assertEquals(visibleFromAt, saved.visibleFromAtUtc)
|
assertEquals(88.5, saved.finalScore, 0.0001)
|
||||||
assertEquals(2L, saved.creatorId)
|
assertEquals(CreatorRankingScoreSpec.SCORE_POLICY_VERSION, saved.scorePolicyVersion)
|
||||||
|
assertEquals("{\"liveCanAmount\":10}", saved.scoreDetailJson)
|
||||||
|
|
||||||
|
val mapped = adapter.findSnapshotsByAggregationPeriod(START, END).single()
|
||||||
|
assertEquals(2, mapped.rankNo)
|
||||||
|
assertEquals(88.5, mapped.finalScore, 0.0001)
|
||||||
|
assertEquals(CreatorRankingScoreSpec.SCORE_POLICY_VERSION, mapped.scorePolicyVersion)
|
||||||
|
assertEquals("{\"liveCanAmount\":10}", mapped.scoreDetailJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("최신 완료 주차 스냅샷은 최신 종료 시각 기준으로 최종 점수 내림차순 조회한다")
|
@DisplayName("findLatestSnapshots는 최신 종료 시각 스냅샷을 rank_no 오름차순으로 조회한다")
|
||||||
fun shouldFindLatestSnapshotsByLatestAggregationEndAndFinalScoreDescending() {
|
fun shouldFindLatestSnapshotsOrderedByRankNoAsc() {
|
||||||
val oldStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
|
||||||
val oldEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
repository.saveAll(
|
repository.saveAll(
|
||||||
listOf(
|
listOf(
|
||||||
snapshot(
|
entity(
|
||||||
creatorId = 1L,
|
creatorId = 1L,
|
||||||
|
rankNo = 1,
|
||||||
finalScore = 999.0,
|
finalScore = 999.0,
|
||||||
aggregationStartAtUtc = oldStartAt,
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
aggregationEndAtUtc = oldEndAt
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
),
|
),
|
||||||
snapshot(
|
entity(creatorId = 2L, rankNo = 3, finalScore = 300.0),
|
||||||
creatorId = 2L,
|
entity(creatorId = 3L, rankNo = 1, finalScore = 100.0),
|
||||||
finalScore = 100.0,
|
entity(creatorId = 4L, rankNo = 2, finalScore = 500.0)
|
||||||
aggregationStartAtUtc = latestStartAt,
|
|
||||||
aggregationEndAtUtc = latestEndAt
|
|
||||||
),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 3L,
|
|
||||||
finalScore = 300.0,
|
|
||||||
aggregationStartAtUtc = latestStartAt,
|
|
||||||
aggregationEndAtUtc = latestEndAt
|
|
||||||
),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 4L,
|
|
||||||
finalScore = 200.0,
|
|
||||||
aggregationStartAtUtc = latestStartAt,
|
|
||||||
aggregationEndAtUtc = latestEndAt
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val latestSnapshots = adapter.findLatestSnapshots()
|
val latestSnapshots = adapter.findLatestSnapshots()
|
||||||
|
|
||||||
assertEquals(listOf(3L, 4L, 2L), latestSnapshots.map { it.creatorId })
|
assertEquals(listOf(3L, 4L, 2L), latestSnapshots.map { it.creatorId })
|
||||||
assertEquals(listOf(latestStartAt, latestStartAt, latestStartAt), latestSnapshots.map { it.aggregationStartAtUtc })
|
assertEquals(listOf(1, 2, 3), latestSnapshots.map { it.rankNo })
|
||||||
assertEquals(listOf(latestEndAt, latestEndAt, latestEndAt), latestSnapshots.map { it.aggregationEndAtUtc })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("최신 공개 스냅샷은 visibleFromAt이 현재 시각 이하인 최신 노출 시각 기준으로 조회한다")
|
@DisplayName("findLatestVisibleSnapshots는 현재 공개 가능한 최신 visible_from_at 스냅샷을 rank_no 오름차순으로 조회한다")
|
||||||
fun shouldFindLatestVisibleSnapshotsByVisibleFromAt() {
|
fun shouldFindLatestVisibleSnapshotsOrderedByRankNoAsc() {
|
||||||
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
|
||||||
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
repository.saveAll(
|
repository.saveAll(
|
||||||
listOf(
|
listOf(
|
||||||
snapshot(
|
entity(creatorId = 1L, rankNo = 1, finalScore = 999.0, visibleFromAtUtc = VISIBLE.plusDays(1)),
|
||||||
|
entity(creatorId = 2L, rankNo = 3, finalScore = 300.0, visibleFromAtUtc = VISIBLE),
|
||||||
|
entity(creatorId = 3L, rankNo = 1, finalScore = 100.0, visibleFromAtUtc = VISIBLE),
|
||||||
|
entity(creatorId = 4L, rankNo = 2, finalScore = 500.0, visibleFromAtUtc = VISIBLE),
|
||||||
|
entity(
|
||||||
|
creatorId = 5L,
|
||||||
|
rankNo = 1,
|
||||||
|
finalScore = 700.0,
|
||||||
|
visibleFromAtUtc = VISIBLE.minusDays(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val latestVisible = adapter.findLatestVisibleSnapshots(CreatorRankingType.WEEKLY, NOW)
|
||||||
|
|
||||||
|
assertEquals(listOf(3L, 4L, 2L), latestVisible.map { it.creatorId })
|
||||||
|
assertEquals(listOf(1, 2, 3), latestVisible.map { it.rankNo })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("findPreviousVisibleSnapshots는 현재 집계 시작 전 공개 스냅샷을 rank_no 오름차순으로 조회한다")
|
||||||
|
fun shouldFindPreviousVisibleSnapshotsOrderedByRankNoAsc() {
|
||||||
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
|
entity(
|
||||||
creatorId = 1L,
|
creatorId = 1L,
|
||||||
aggregationStartAtUtc = previousStartAt,
|
rankNo = 1,
|
||||||
aggregationEndAtUtc = previousEndAt,
|
aggregationStartAtUtc = OLDEST_START,
|
||||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 1, 0, 0),
|
aggregationEndAtUtc = OLDEST_END,
|
||||||
finalScore = 100.0
|
visibleFromAtUtc = VISIBLE.minusWeeks(1)
|
||||||
),
|
),
|
||||||
snapshot(
|
entity(
|
||||||
creatorId = 2L,
|
creatorId = 2L,
|
||||||
aggregationStartAtUtc = latestStartAt,
|
rankNo = 3,
|
||||||
aggregationEndAtUtc = latestEndAt,
|
|
||||||
visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0),
|
|
||||||
finalScore = 200.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val beforeVisible = adapter.findLatestVisibleSnapshots(
|
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
|
||||||
nowUtc = LocalDateTime.of(2026, 6, 7, 23, 59, 59)
|
|
||||||
)
|
|
||||||
val afterVisible = adapter.findLatestVisibleSnapshots(
|
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
|
||||||
nowUtc = LocalDateTime.of(2026, 6, 8, 0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(listOf(1L), beforeVisible.map { it.creatorId })
|
|
||||||
assertEquals(listOf(2L), afterVisible.map { it.creatorId })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("직전 공개 스냅샷은 현재 공개 스냅샷보다 이전 집계 시작 시각 중 최신 공개 기준으로 조회한다")
|
|
||||||
fun shouldFindPreviousVisibleSnapshotsBeforeCurrentVisibleSnapshot() {
|
|
||||||
val oldestStartAt = LocalDateTime.of(2026, 5, 17, 15, 0)
|
|
||||||
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
|
||||||
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
repository.saveAll(
|
|
||||||
listOf(
|
|
||||||
snapshot(creatorId = 1L, aggregationStartAtUtc = oldestStartAt, aggregationEndAtUtc = oldestStartAt.plusWeeks(1)),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 2L,
|
|
||||||
aggregationStartAtUtc = previousStartAt,
|
|
||||||
aggregationEndAtUtc = previousStartAt.plusWeeks(1),
|
|
||||||
finalScore = 300.0
|
|
||||||
),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 3L,
|
|
||||||
aggregationStartAtUtc = previousStartAt,
|
|
||||||
aggregationEndAtUtc = previousStartAt.plusWeeks(1),
|
|
||||||
finalScore = 200.0
|
|
||||||
),
|
|
||||||
snapshot(creatorId = 4L, aggregationStartAtUtc = latestStartAt, aggregationEndAtUtc = latestStartAt.plusWeeks(1))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val previous = adapter.findPreviousVisibleSnapshots(
|
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
|
||||||
currentAggregationStartAtUtc = latestStartAt,
|
|
||||||
nowUtc = LocalDateTime.of(2026, 6, 8, 0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(listOf(2L, 3L), previous.map { it.creatorId })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("직전 완료 주차 스냅샷은 최신 종료 시각보다 이전인 가장 큰 종료 시각 기준으로 조회한다")
|
|
||||||
fun shouldFindPreviousCompletedSnapshotsBeforeLatestPeriod() {
|
|
||||||
val oldestStartAt = LocalDateTime.of(2026, 5, 17, 15, 0)
|
|
||||||
val oldestEndAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
|
||||||
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
|
||||||
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val latestEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
repository.saveAll(
|
|
||||||
listOf(
|
|
||||||
snapshot(creatorId = 1L, aggregationStartAtUtc = oldestStartAt, aggregationEndAtUtc = oldestEndAt),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 2L,
|
|
||||||
finalScore = 200.0,
|
|
||||||
aggregationStartAtUtc = previousStartAt,
|
|
||||||
aggregationEndAtUtc = previousEndAt
|
|
||||||
),
|
|
||||||
snapshot(
|
|
||||||
creatorId = 3L,
|
|
||||||
finalScore = 300.0,
|
finalScore = 300.0,
|
||||||
aggregationStartAtUtc = previousStartAt,
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
aggregationEndAtUtc = previousEndAt
|
aggregationEndAtUtc = PREVIOUS_END,
|
||||||
|
visibleFromAtUtc = VISIBLE.minusDays(1)
|
||||||
),
|
),
|
||||||
snapshot(creatorId = 4L, aggregationStartAtUtc = latestStartAt, aggregationEndAtUtc = latestEndAt)
|
entity(
|
||||||
|
creatorId = 3L,
|
||||||
|
rankNo = 1,
|
||||||
|
finalScore = 100.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END,
|
||||||
|
visibleFromAtUtc = VISIBLE.minusDays(1)
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 4L,
|
||||||
|
rankNo = 2,
|
||||||
|
finalScore = 500.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END,
|
||||||
|
visibleFromAtUtc = VISIBLE.minusDays(1)
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 5L,
|
||||||
|
rankNo = 1,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END,
|
||||||
|
visibleFromAtUtc = VISIBLE.plusDays(1)
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 6L,
|
||||||
|
rankNo = 1,
|
||||||
|
aggregationStartAtUtc = START,
|
||||||
|
aggregationEndAtUtc = END,
|
||||||
|
visibleFromAtUtc = VISIBLE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val previousSnapshots = adapter.findPreviousCompletedSnapshots()
|
val previousVisible = adapter.findPreviousVisibleSnapshots(CreatorRankingType.WEEKLY, START, NOW)
|
||||||
|
|
||||||
assertEquals(listOf(3L, 2L), previousSnapshots.map { it.creatorId })
|
assertEquals(listOf(3L, 4L, 2L), previousVisible.map { it.creatorId })
|
||||||
assertEquals(listOf(previousEndAt, previousEndAt), previousSnapshots.map { it.aggregationEndAtUtc })
|
assertEquals(listOf(1, 2, 3), previousVisible.map { it.rankNo })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("요청한 집계 기간에 스냅샷이 없으면 이전 주차를 대신 반환하지 않는다")
|
@DisplayName("findPreviousCompletedSnapshots는 최신 완료 전 집계 종료 시각 스냅샷을 rank_no 오름차순으로 조회한다")
|
||||||
fun shouldReturnEmptyWhenRequestedAggregationPeriodHasNoSnapshots() {
|
fun shouldFindPreviousCompletedSnapshotsOrderedByRankNoAsc() {
|
||||||
val previousStartAt = LocalDateTime.of(2026, 5, 24, 15, 0)
|
repository.saveAll(
|
||||||
val previousEndAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
listOf(
|
||||||
val requestedStartAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
entity(creatorId = 1L, rankNo = 1, aggregationStartAtUtc = OLDEST_START, aggregationEndAtUtc = OLDEST_END),
|
||||||
val requestedEndAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
entity(
|
||||||
repository.save(
|
creatorId = 2L,
|
||||||
snapshot(
|
rankNo = 3,
|
||||||
|
finalScore = 300.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 3L,
|
||||||
|
rankNo = 1,
|
||||||
|
finalScore = 100.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 4L,
|
||||||
|
rankNo = 2,
|
||||||
|
finalScore = 500.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
|
),
|
||||||
|
entity(creatorId = 5L, rankNo = 1, aggregationStartAtUtc = START, aggregationEndAtUtc = END)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val previousCompleted = adapter.findPreviousCompletedSnapshots()
|
||||||
|
|
||||||
|
assertEquals(listOf(3L, 4L, 2L), previousCompleted.map { it.creatorId })
|
||||||
|
assertEquals(listOf(1, 2, 3), previousCompleted.map { it.rankNo })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("findSnapshotsByAggregationPeriod는 요청 기간 스냅샷을 rank_no 오름차순으로 조회한다")
|
||||||
|
fun shouldFindSnapshotsByAggregationPeriodOrderedByRankNoAsc() {
|
||||||
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
|
entity(
|
||||||
creatorId = 1L,
|
creatorId = 1L,
|
||||||
aggregationStartAtUtc = previousStartAt,
|
rankNo = 1,
|
||||||
aggregationEndAtUtc = previousEndAt
|
finalScore = 999.0,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
|
),
|
||||||
|
entity(creatorId = 2L, rankNo = 3, finalScore = 300.0),
|
||||||
|
entity(creatorId = 3L, rankNo = 1, finalScore = 100.0),
|
||||||
|
entity(creatorId = 4L, rankNo = 2, finalScore = 500.0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val snapshots = adapter.findSnapshotsByAggregationPeriod(
|
val periodSnapshots = adapter.findSnapshotsByAggregationPeriod(START, END)
|
||||||
aggregationStartAtUtc = requestedStartAt,
|
|
||||||
aggregationEndAtUtc = requestedEndAt
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals(emptyList<CreatorRankingSnapshotRecord>(), snapshots)
|
assertEquals(listOf(3L, 4L, 2L), periodSnapshots.map { it.creatorId })
|
||||||
|
assertEquals(listOf(1, 2, 3), periodSnapshots.map { it.rankNo })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("스냅샷 row가 하나도 없을 때만 테이블 완전 공백으로 판단한다")
|
@DisplayName("replaceSnapshots는 같은 rankingType과 집계 기간만 삭제하고 다른 기간은 유지한다")
|
||||||
fun shouldReturnTrueOnlyWhenSnapshotTableHasNoRows() {
|
fun shouldReplaceOnlySameTypeAndAggregationPeriod() {
|
||||||
assertEquals(true, adapter.isSnapshotTableEmpty())
|
repository.saveAll(
|
||||||
|
listOf(
|
||||||
repository.save(
|
entity(
|
||||||
snapshot(
|
|
||||||
creatorId = 1L,
|
creatorId = 1L,
|
||||||
aggregationStartAtUtc = LocalDateTime.of(2026, 5, 24, 15, 0),
|
rankNo = 1,
|
||||||
aggregationEndAtUtc = LocalDateTime.of(2026, 5, 31, 15, 0)
|
rankingType = CreatorRankingType.WEEKLY,
|
||||||
|
aggregationStartAtUtc = START,
|
||||||
|
aggregationEndAtUtc = END
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 2L,
|
||||||
|
rankNo = 1,
|
||||||
|
rankingType = CreatorRankingType.WEEKLY,
|
||||||
|
aggregationStartAtUtc = PREVIOUS_START,
|
||||||
|
aggregationEndAtUtc = PREVIOUS_END
|
||||||
|
),
|
||||||
|
entity(
|
||||||
|
creatorId = 3L,
|
||||||
|
rankNo = 1,
|
||||||
|
rankingType = CreatorRankingType.WEEKLY,
|
||||||
|
aggregationStartAtUtc = OLDEST_START,
|
||||||
|
aggregationEndAtUtc = OLDEST_END
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(false, adapter.isSnapshotTableEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("20위 점수 경계 동점 후보는 저장소에서 누락 없이 저장하고 조회할 수 있다")
|
|
||||||
fun shouldPersistAllCandidatesTiedAtTwentiethScoreBoundary() {
|
|
||||||
val startAt = LocalDateTime.of(2026, 5, 31, 15, 0)
|
|
||||||
val endAt = LocalDateTime.of(2026, 6, 7, 15, 0)
|
|
||||||
val candidates = (1L..19L).map { creatorId ->
|
|
||||||
snapshotRecord(
|
|
||||||
creatorId = creatorId,
|
|
||||||
finalScore = 1000.0 - creatorId,
|
|
||||||
aggregationStartAtUtc = startAt,
|
|
||||||
aggregationEndAtUtc = endAt
|
|
||||||
)
|
|
||||||
} + listOf(
|
|
||||||
snapshotRecord(creatorId = 20L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt),
|
|
||||||
snapshotRecord(creatorId = 21L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt),
|
|
||||||
snapshotRecord(creatorId = 22L, finalScore = 500.0, aggregationStartAtUtc = startAt, aggregationEndAtUtc = endAt)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter.replaceSnapshots(
|
adapter.replaceSnapshots(
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
rankingType = CreatorRankingType.WEEKLY,
|
||||||
aggregationStartAtUtc = startAt,
|
aggregationStartAtUtc = START,
|
||||||
aggregationEndAtUtc = endAt,
|
aggregationEndAtUtc = END,
|
||||||
visibleFromAtUtc = endAt.plusHours(9),
|
visibleFromAtUtc = VISIBLE,
|
||||||
newSnapshots = candidates
|
newSnapshots = listOf(record(creatorId = 4L, rankNo = 1))
|
||||||
)
|
)
|
||||||
|
|
||||||
val latestSnapshots = adapter.findLatestSnapshots()
|
assertEquals(listOf(2L, 3L, 4L), repository.findAll().map { it.creatorId }.sorted())
|
||||||
assertEquals(22, latestSnapshots.size)
|
|
||||||
assertEquals(setOf(20L, 21L, 22L), latestSnapshots.takeLast(3).map { it.creatorId }.toSet())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun snapshot(
|
private fun record(
|
||||||
creatorId: Long,
|
creatorId: Long,
|
||||||
finalScore: Double = 100.0,
|
rankNo: Int,
|
||||||
aggregationStartAtUtc: LocalDateTime,
|
finalScore: Double = (100 - rankNo).toDouble(),
|
||||||
aggregationEndAtUtc: LocalDateTime,
|
rankingType: CreatorRankingType = CreatorRankingType.WEEKLY,
|
||||||
visibleFromAtUtc: LocalDateTime = aggregationEndAtUtc.plusHours(9)
|
aggregationStartAtUtc: LocalDateTime = START,
|
||||||
): CreatorRankingSnapshot {
|
aggregationEndAtUtc: LocalDateTime = END,
|
||||||
return CreatorRankingSnapshot(
|
visibleFromAtUtc: LocalDateTime = VISIBLE,
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
scorePolicyVersion: String = CreatorRankingScoreSpec.SCORE_POLICY_VERSION,
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
scoreDetailJson: String = "{}"
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
|
||||||
creatorId = creatorId,
|
|
||||||
nickname = "creator-$creatorId",
|
|
||||||
profileImageUrl = "profile-$creatorId.png",
|
|
||||||
finalScore = finalScore,
|
|
||||||
contentLiveScore = 10.0,
|
|
||||||
engagementScore = 20.0,
|
|
||||||
supportScore = 30.0,
|
|
||||||
fanLoyaltyScore = 40.0,
|
|
||||||
liveCanAmount = 100,
|
|
||||||
contentPurchaseCanAmount = 200,
|
|
||||||
contentLikeCount = 3,
|
|
||||||
contentCommentCount = 4,
|
|
||||||
channelDonationCanAmount = 500,
|
|
||||||
channelDonationCount = 6,
|
|
||||||
fanTalkCount = 7,
|
|
||||||
finalFollowerCount = 8,
|
|
||||||
followIncrease = -1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun snapshotRecord(
|
|
||||||
creatorId: Long,
|
|
||||||
finalScore: Double = 100.0,
|
|
||||||
aggregationStartAtUtc: LocalDateTime,
|
|
||||||
aggregationEndAtUtc: LocalDateTime,
|
|
||||||
visibleFromAtUtc: LocalDateTime = aggregationEndAtUtc.plusHours(9)
|
|
||||||
): CreatorRankingSnapshotRecord {
|
): CreatorRankingSnapshotRecord {
|
||||||
return CreatorRankingSnapshotRecord(
|
return CreatorRankingSnapshotRecord(
|
||||||
rankingType = CreatorRankingType.WEEKLY,
|
rankingType = rankingType,
|
||||||
aggregationStartAtUtc = aggregationStartAtUtc,
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
aggregationEndAtUtc = aggregationEndAtUtc,
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
visibleFromAtUtc = visibleFromAtUtc,
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
nickname = "creator-$creatorId",
|
nickname = "creator-$creatorId",
|
||||||
profileImageUrl = "profile-$creatorId.png",
|
profileImageUrl = "profile-$creatorId.png",
|
||||||
|
rankNo = rankNo,
|
||||||
finalScore = finalScore,
|
finalScore = finalScore,
|
||||||
contentLiveScore = 10.0,
|
scorePolicyVersion = scorePolicyVersion,
|
||||||
engagementScore = 20.0,
|
scoreDetailJson = scoreDetailJson
|
||||||
supportScore = 30.0,
|
|
||||||
fanLoyaltyScore = 40.0,
|
|
||||||
liveCanAmount = 100,
|
|
||||||
contentPurchaseCanAmount = 200,
|
|
||||||
contentLikeCount = 3,
|
|
||||||
contentCommentCount = 4,
|
|
||||||
channelDonationCanAmount = 500,
|
|
||||||
channelDonationCount = 6,
|
|
||||||
fanTalkCount = 7,
|
|
||||||
finalFollowerCount = 8,
|
|
||||||
followIncrease = -1
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun entity(
|
||||||
|
creatorId: Long,
|
||||||
|
rankNo: Int,
|
||||||
|
finalScore: Double = (100 - rankNo).toDouble(),
|
||||||
|
rankingType: CreatorRankingType = CreatorRankingType.WEEKLY,
|
||||||
|
aggregationStartAtUtc: LocalDateTime = START,
|
||||||
|
aggregationEndAtUtc: LocalDateTime = END,
|
||||||
|
visibleFromAtUtc: LocalDateTime = VISIBLE,
|
||||||
|
scorePolicyVersion: String = CreatorRankingScoreSpec.SCORE_POLICY_VERSION,
|
||||||
|
scoreDetailJson: String = "{}"
|
||||||
|
): CreatorRankingSnapshot {
|
||||||
|
return CreatorRankingSnapshot(
|
||||||
|
rankingType = rankingType,
|
||||||
|
aggregationStartAtUtc = aggregationStartAtUtc,
|
||||||
|
aggregationEndAtUtc = aggregationEndAtUtc,
|
||||||
|
visibleFromAtUtc = visibleFromAtUtc,
|
||||||
|
creatorId = creatorId,
|
||||||
|
nickname = "creator-$creatorId",
|
||||||
|
profileImageUrl = "profile-$creatorId.png",
|
||||||
|
rankNo = rankNo,
|
||||||
|
finalScore = finalScore,
|
||||||
|
scorePolicyVersion = scorePolicyVersion,
|
||||||
|
scoreDetailJson = scoreDetailJson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val OLDEST_START = LocalDateTime.of(2026, 5, 17, 15, 0)
|
||||||
|
private val OLDEST_END = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
private val PREVIOUS_START = LocalDateTime.of(2026, 5, 24, 15, 0)
|
||||||
|
private val PREVIOUS_END = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
private val START = LocalDateTime.of(2026, 5, 31, 15, 0)
|
||||||
|
private val END = LocalDateTime.of(2026, 6, 7, 15, 0)
|
||||||
|
private val VISIBLE = LocalDateTime.of(2026, 6, 8, 0, 0)
|
||||||
|
private val NOW = LocalDateTime.of(2026, 6, 8, 1, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user