the-algorithm/home-mixer/server/src/main/scala/com/twitter/home_mixer/util/CandidatesUtil.scala
twitter-team ef4c5eb65e Twitter Recommendation Algorithm
Please note we have force-pushed a new initial commit in order to remove some publicly-available Twitter user information. Note that this process may be required in the future.
2023-03-31 17:36:31 -05:00

106 lines
5.1 KiB
Scala

package com.twitter.home_mixer.util
import com.twitter.home_mixer.model.HomeFeatures.AuthorIdFeature
import com.twitter.home_mixer.model.HomeFeatures.FavoritedByUserIdsFeature
import com.twitter.home_mixer.model.HomeFeatures.HasImageFeature
import com.twitter.home_mixer.model.HomeFeatures.IsRetweetFeature
import com.twitter.home_mixer.model.HomeFeatures.MediaUnderstandingAnnotationIdsFeature
import com.twitter.home_mixer.model.HomeFeatures.RepliedByEngagerIdsFeature
import com.twitter.home_mixer.model.HomeFeatures.RetweetedByEngagerIdsFeature
import com.twitter.home_mixer.model.HomeFeatures.ScoreFeature
import com.twitter.home_mixer.model.HomeFeatures.SourceTweetIdFeature
import com.twitter.home_mixer.model.HomeFeatures.SourceUserIdFeature
import com.twitter.product_mixer.component_library.model.candidate.CursorCandidate
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
import com.twitter.product_mixer.core.model.common.UniversalNoun
import com.twitter.product_mixer.core.model.common.presentation.CandidateWithDetails
import com.twitter.product_mixer.core.model.common.presentation.ItemCandidateWithDetails
import com.twitter.product_mixer.core.model.common.presentation.ModuleCandidateWithDetails
import com.twitter.product_mixer.core.pipeline.PipelineQuery
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
import com.twitter.product_mixer.core.pipeline.pipeline_failure.UnexpectedCandidateResult
import scala.reflect.ClassTag
object CandidatesUtil {
def getItemCandidates(candidates: Seq[CandidateWithDetails]): Seq[ItemCandidateWithDetails] = {
candidates.collect {
case item: ItemCandidateWithDetails if !item.isCandidateType[CursorCandidate] => Seq(item)
case module: ModuleCandidateWithDetails => module.candidates
}.flatten
}
def getItemCandidatesWithOnlyModuleLast(
candidates: Seq[CandidateWithDetails]
): Seq[ItemCandidateWithDetails] = {
candidates.collect {
case item: ItemCandidateWithDetails if !item.isCandidateType[CursorCandidate] => item
case module: ModuleCandidateWithDetails => module.candidates.last
}
}
def containsType[CandidateType <: UniversalNoun[_]](
candidates: Seq[CandidateWithDetails]
)(
implicit tag: ClassTag[CandidateType]
): Boolean = candidates.exists {
case ItemCandidateWithDetails(_: CandidateType, _, _) => true
case module: ModuleCandidateWithDetails =>
module.candidates.head.isCandidateType[CandidateType]()
case _ => false
}
def getOriginalAuthorId(candidateFeatures: FeatureMap): Option[Long] =
if (candidateFeatures.getOrElse(IsRetweetFeature, false))
candidateFeatures.getOrElse(SourceUserIdFeature, None)
else candidateFeatures.getOrElse(AuthorIdFeature, None)
def getEngagerUserIds(
candidateFeatures: FeatureMap
): Seq[Long] = {
candidateFeatures.getOrElse(FavoritedByUserIdsFeature, Seq.empty) ++
candidateFeatures.getOrElse(RetweetedByEngagerIdsFeature, Seq.empty) ++
candidateFeatures.getOrElse(RepliedByEngagerIdsFeature, Seq.empty)
}
def getMediaUnderstandingAnnotationIds(
candidateFeatures: FeatureMap
): Seq[Long] = {
if (candidateFeatures.get(HasImageFeature))
candidateFeatures.getOrElse(MediaUnderstandingAnnotationIdsFeature, Seq.empty)
else Seq.empty
}
def getTweetIdAndSourceId(candidate: CandidateWithFeatures[TweetCandidate]): Seq[Long] =
Seq(candidate.candidate.id) ++ candidate.features.getOrElse(SourceTweetIdFeature, None)
def isAuthoredByViewer(query: PipelineQuery, candidateFeatures: FeatureMap): Boolean =
candidateFeatures.getOrElse(AuthorIdFeature, None).contains(query.getRequiredUserId) ||
(candidateFeatures.getOrElse(IsRetweetFeature, false) &&
candidateFeatures.getOrElse(SourceUserIdFeature, None).contains(query.getRequiredUserId))
val reverseChronTweetsOrdering: Ordering[CandidateWithDetails] =
Ordering.by[CandidateWithDetails, Long] {
case ItemCandidateWithDetails(candidate: TweetCandidate, _, _) => -candidate.id
case ModuleCandidateWithDetails(candidates, _, _) if candidates.nonEmpty =>
-candidates.last.candidateIdLong
case _ => throw PipelineFailure(UnexpectedCandidateResult, "Invalid candidate type")
}
val scoreOrdering: Ordering[CandidateWithDetails] = Ordering.by[CandidateWithDetails, Double] {
case ItemCandidateWithDetails(_, _, features) =>
-features.getOrElse(ScoreFeature, None).getOrElse(0.0)
case ModuleCandidateWithDetails(candidates, _, _) =>
-candidates.last.features.getOrElse(ScoreFeature, None).getOrElse(0.0)
case _ => throw PipelineFailure(UnexpectedCandidateResult, "Invalid candidate type")
}
val conversationModuleTweetsOrdering: Ordering[CandidateWithDetails] =
Ordering.by[CandidateWithDetails, Long] {
case ItemCandidateWithDetails(candidate: TweetCandidate, _, _) => candidate.id
case _ => throw PipelineFailure(UnexpectedCandidateResult, "Only Item candidate expected")
}
}