feat(home): 크리에이터 랭킹 후보 집계를 변경한다
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.persistence
|
||||
|
||||
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.port.out.CreatorRankingAggregationPort
|
||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.sql.Timestamp
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@@ -12,8 +12,6 @@ import javax.persistence.EntityManager
|
||||
class DefaultCreatorRankingAggregationRepository(
|
||||
private val entityManager: EntityManager
|
||||
) : CreatorRankingAggregationPort {
|
||||
private val scorePolicy = CreatorRankingScorePolicy()
|
||||
|
||||
override fun aggregateCandidates(
|
||||
startInclusiveUtc: LocalDateTime,
|
||||
endExclusiveUtc: LocalDateTime
|
||||
@@ -26,13 +24,9 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
endExclusiveUtc: LocalDateTime
|
||||
): CreatorRankingAggregationResult {
|
||||
val candidates = aggregateAllCandidates(startInclusiveUtc, endExclusiveUtc)
|
||||
val includedCandidates = candidates
|
||||
.filter { candidate -> candidate.finalScore >= MINIMUM_FINAL_SCORE }
|
||||
.sortedWith(compareByDescending<CreatorRankingSnapshotCandidate> { it.finalScore }.thenBy { it.creatorId })
|
||||
|
||||
return CreatorRankingAggregationResult(
|
||||
candidates = includedCandidates,
|
||||
lowScoreExcludedCount = candidates.size - includedCandidates.size
|
||||
candidates = candidates,
|
||||
lowScoreExcludedCount = 0
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,42 +43,15 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
}
|
||||
|
||||
private fun Array<*>.toCandidate(): CreatorRankingSnapshotCandidate {
|
||||
val creatorId = this[0].toLong()
|
||||
val nickname = this[1] as String
|
||||
val profileImageUrl = this[2] as String?
|
||||
val liveCanAmount = this[3].toLong()
|
||||
val contentPurchaseCanAmount = this[4].toLong()
|
||||
val contentLikeCount = this[5].toLong()
|
||||
val contentCommentCount = this[6].toLong()
|
||||
val channelDonationCanAmount = this[7].toLong()
|
||||
val channelDonationCount = this[8].toLong()
|
||||
val fanTalkCount = this[9].toLong()
|
||||
val finalFollowerCount = this[10].toLong()
|
||||
val followIncrease = this[11].toLong()
|
||||
val contentLiveScore = scorePolicy.calculateContentLiveScore(liveCanAmount, contentPurchaseCanAmount)
|
||||
val engagementScore = scorePolicy.calculateEngagementScore(contentLikeCount, contentCommentCount)
|
||||
val supportScore = scorePolicy.calculateSupportScore(channelDonationCanAmount, channelDonationCount, fanTalkCount)
|
||||
val fanLoyaltyScore = scorePolicy.calculateFanLoyaltyScore(finalFollowerCount, followIncrease)
|
||||
val finalScore = scorePolicy.calculateFinalScore(contentLiveScore, engagementScore, supportScore, fanLoyaltyScore)
|
||||
|
||||
return CreatorRankingSnapshotCandidate(
|
||||
creatorId = creatorId,
|
||||
nickname = nickname,
|
||||
profileImageUrl = profileImageUrl,
|
||||
finalScore = finalScore,
|
||||
contentLiveScore = contentLiveScore,
|
||||
engagementScore = engagementScore,
|
||||
supportScore = supportScore,
|
||||
fanLoyaltyScore = fanLoyaltyScore,
|
||||
liveCanAmount = liveCanAmount,
|
||||
contentPurchaseCanAmount = contentPurchaseCanAmount,
|
||||
contentLikeCount = contentLikeCount,
|
||||
contentCommentCount = contentCommentCount,
|
||||
channelDonationCanAmount = channelDonationCanAmount,
|
||||
channelDonationCount = channelDonationCount,
|
||||
fanTalkCount = fanTalkCount,
|
||||
finalFollowerCount = finalFollowerCount,
|
||||
followIncrease = followIncrease
|
||||
creatorId = this[0].toLong(),
|
||||
nickname = this[1] as String,
|
||||
profileImageUrl = this[2] as String?,
|
||||
creatorDebutAt = this[3].toLocalDateTimeOrNull(),
|
||||
liveCanAmount = this[4].toLong(),
|
||||
contentPurchaseCanAmount = this[5].toLong(),
|
||||
followIncrease = this[6].toLong().coerceAtLeast(0L),
|
||||
aiChatCount = this[7].toLong()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,9 +59,16 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
return (this as Number?)?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MINIMUM_FINAL_SCORE = 1.0
|
||||
private fun Any?.toLocalDateTimeOrNull(): LocalDateTime? {
|
||||
return when (this) {
|
||||
null -> null
|
||||
is LocalDateTime -> this
|
||||
is Timestamp -> toLocalDateTime()
|
||||
else -> throw IllegalStateException("Unsupported creator debut timestamp type: ${this::class.java.name}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val AGGREGATION_SQL = """
|
||||
with active_creators as (
|
||||
select id, nickname, profile_image
|
||||
@@ -104,9 +78,7 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
), can_metrics as (
|
||||
select ucc.recipient_creator_id as creator_id,
|
||||
sum(case when uc.can_usage in ('DONATION', 'LIVE', 'SPIN_ROULETTE') then ucc.can else 0 end) as live_can_amount,
|
||||
sum(case when uc.can_usage = 'ORDER_CONTENT' then ucc.can else 0 end) as content_purchase_can_amount,
|
||||
sum(case when uc.can_usage = 'CHANNEL_DONATION' then ucc.can else 0 end) as channel_donation_can_amount,
|
||||
sum(case when uc.can_usage = 'CHANNEL_DONATION' then 1 else 0 end) as channel_donation_count
|
||||
sum(case when uc.can_usage = 'ORDER_CONTENT' then ucc.can else 0 end) as content_purchase_can_amount
|
||||
from use_can_calculate ucc
|
||||
join use_can uc on uc.id = ucc.use_can_id
|
||||
where ucc.recipient_creator_id is not null
|
||||
@@ -115,43 +87,6 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
and ucc.created_at >= :startInclusiveUtc
|
||||
and ucc.created_at < :endExclusiveUtc
|
||||
group by ucc.recipient_creator_id
|
||||
), like_metrics as (
|
||||
select c.member_id as creator_id,
|
||||
count(cl.id) as content_like_count
|
||||
from content_like cl
|
||||
join content c on c.id = cl.content_id
|
||||
where c.is_active = true
|
||||
and cl.is_active = true
|
||||
and cl.created_at >= :startInclusiveUtc
|
||||
and cl.created_at < :endExclusiveUtc
|
||||
group by c.member_id
|
||||
), comment_metrics as (
|
||||
select c.member_id as creator_id,
|
||||
count(cc.id) as content_comment_count
|
||||
from content_comment cc
|
||||
join content c on c.id = cc.content_id
|
||||
where c.is_active = true
|
||||
and cc.is_active = true
|
||||
and cc.member_id <> c.member_id
|
||||
and cc.created_at >= :startInclusiveUtc
|
||||
and cc.created_at < :endExclusiveUtc
|
||||
group by c.member_id
|
||||
), fan_talk_metrics as (
|
||||
select creator_id,
|
||||
count(id) as fan_talk_count
|
||||
from creator_cheers
|
||||
where is_active = true
|
||||
and parent_id is null
|
||||
and created_at >= :startInclusiveUtc
|
||||
and created_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), final_follower_metrics as (
|
||||
select creator_id,
|
||||
count(id) as final_follower_count
|
||||
from creator_following
|
||||
where is_active = true
|
||||
and created_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), new_follow_metrics as (
|
||||
select creator_id,
|
||||
count(id) as new_follow_count
|
||||
@@ -167,37 +102,68 @@ class DefaultCreatorRankingAggregationRepository(
|
||||
and updated_at >= :startInclusiveUtc
|
||||
and updated_at < :endExclusiveUtc
|
||||
group by creator_id
|
||||
), ai_chat_metrics as (
|
||||
select creator_member.id as creator_id,
|
||||
count(cm.id) as ai_chat_count
|
||||
from chat_message cm
|
||||
join chat_participant cp on cp.id = cm.participant_id
|
||||
join chat_character cc on cc.id = cp.character_id
|
||||
join member creator_member on creator_member.id = cc.creator_member_id
|
||||
where cm.is_active = true
|
||||
and cp.is_active = true
|
||||
and cp.participant_type = 'CHARACTER'
|
||||
and cc.is_active = true
|
||||
and creator_member.is_active = true
|
||||
and creator_member.role = 'CREATOR'
|
||||
and cm.created_at >= :startInclusiveUtc
|
||||
and cm.created_at < :endExclusiveUtc
|
||||
group by creator_member.id
|
||||
), first_live as (
|
||||
select member_id as creator_id,
|
||||
min(begin_date_time) as first_live_begin_at
|
||||
from live_room
|
||||
where channel_name is not null
|
||||
and begin_date_time is not null
|
||||
and begin_date_time <= current_timestamp
|
||||
group by member_id
|
||||
), first_content as (
|
||||
select member_id as creator_id,
|
||||
min(release_date) as first_content_release_at
|
||||
from content
|
||||
where is_active = true
|
||||
and release_date is not null
|
||||
and release_date <= current_timestamp
|
||||
group by member_id
|
||||
), debut_metrics as (
|
||||
select ac.id as creator_id,
|
||||
case
|
||||
when fl.first_live_begin_at is null then fc.first_content_release_at
|
||||
when fc.first_content_release_at is null then fl.first_live_begin_at
|
||||
when fl.first_live_begin_at <= fc.first_content_release_at then fl.first_live_begin_at
|
||||
else fc.first_content_release_at
|
||||
end as creator_debut_at
|
||||
from active_creators ac
|
||||
left join first_live fl on fl.creator_id = ac.id
|
||||
left join first_content fc on fc.creator_id = ac.id
|
||||
)
|
||||
select ac.id as creator_id,
|
||||
ac.nickname as nickname,
|
||||
ac.profile_image as profile_image_url,
|
||||
dm.creator_debut_at as creator_debut_at,
|
||||
coalesce(cm.live_can_amount, 0) as live_can_amount,
|
||||
coalesce(cm.content_purchase_can_amount, 0) as content_purchase_can_amount,
|
||||
coalesce(lm.content_like_count, 0) as content_like_count,
|
||||
coalesce(com.content_comment_count, 0) as content_comment_count,
|
||||
coalesce(cm.channel_donation_can_amount, 0) as channel_donation_can_amount,
|
||||
coalesce(cm.channel_donation_count, 0) as channel_donation_count,
|
||||
coalesce(ftm.fan_talk_count, 0) as fan_talk_count,
|
||||
coalesce(ffm.final_follower_count, 0) as final_follower_count,
|
||||
coalesce(nfm.new_follow_count, 0) - coalesce(um.unfollow_count, 0) as follow_increase
|
||||
greatest(coalesce(nfm.new_follow_count, 0) - coalesce(um.unfollow_count, 0), 0) as follow_increase,
|
||||
coalesce(acm.ai_chat_count, 0) as ai_chat_count
|
||||
from active_creators ac
|
||||
left join can_metrics cm on cm.creator_id = ac.id
|
||||
left join like_metrics lm on lm.creator_id = ac.id
|
||||
left join comment_metrics com on com.creator_id = ac.id
|
||||
left join fan_talk_metrics ftm on ftm.creator_id = ac.id
|
||||
left join final_follower_metrics ffm on ffm.creator_id = ac.id
|
||||
left join new_follow_metrics nfm on nfm.creator_id = ac.id
|
||||
left join unfollow_metrics um on um.creator_id = ac.id
|
||||
where coalesce(cm.live_can_amount, 0) <> 0
|
||||
or coalesce(cm.content_purchase_can_amount, 0) <> 0
|
||||
or coalesce(lm.content_like_count, 0) <> 0
|
||||
or coalesce(com.content_comment_count, 0) <> 0
|
||||
or coalesce(cm.channel_donation_can_amount, 0) <> 0
|
||||
or coalesce(cm.channel_donation_count, 0) <> 0
|
||||
or coalesce(ftm.fan_talk_count, 0) <> 0
|
||||
or coalesce(ffm.final_follower_count, 0) <> 0
|
||||
or coalesce(nfm.new_follow_count, 0) <> 0
|
||||
or coalesce(um.unfollow_count, 0) <> 0
|
||||
left join ai_chat_metrics acm on acm.creator_id = ac.id
|
||||
left join debut_metrics dm on dm.creator_id = ac.id
|
||||
where coalesce(cm.live_can_amount, 0) > 0
|
||||
or coalesce(cm.content_purchase_can_amount, 0) > 0
|
||||
or greatest(coalesce(nfm.new_follow_count, 0) - coalesce(um.unfollow_count, 0), 0) > 0
|
||||
or coalesce(acm.ai_chat_count, 0) > 0
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
package kr.co.vividnext.sodalive.v2.ranking.domain
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class CreatorRankingSnapshotCandidate(
|
||||
val creatorId: Long,
|
||||
val nickname: String,
|
||||
val profileImageUrl: String?,
|
||||
val finalScore: Double,
|
||||
val contentLiveScore: Double,
|
||||
val engagementScore: Double,
|
||||
val supportScore: Double,
|
||||
val fanLoyaltyScore: Double,
|
||||
val creatorDebutAt: LocalDateTime?,
|
||||
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
|
||||
val followIncrease: Long,
|
||||
val aiChatCount: Long
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user