mirror of
https://github.com/twitter/the-algorithm.git
synced 2024-06-02 17:28:45 +02:00
b389c3d302
Pushservice is the main recommendation service we use to surface recommendations to our users via notifications. It fetches candidates from various sources, ranks them in order of relevance, and applies filters to determine the best one to send.
121 lines
5.1 KiB
Scala
121 lines
5.1 KiB
Scala
package com.twitter.frigate.pushservice.adaptor
|
|
|
|
import com.twitter.explore_ranker.thriftscala.ExploreRankerProductResponse
|
|
import com.twitter.explore_ranker.thriftscala.ExploreRankerRequest
|
|
import com.twitter.explore_ranker.thriftscala.ExploreRankerResponse
|
|
import com.twitter.explore_ranker.thriftscala.ExploreRecommendation
|
|
import com.twitter.explore_ranker.thriftscala.ImmersiveRecsResponse
|
|
import com.twitter.explore_ranker.thriftscala.ImmersiveRecsResult
|
|
import com.twitter.explore_ranker.thriftscala.NotificationsVideoRecs
|
|
import com.twitter.explore_ranker.thriftscala.Product
|
|
import com.twitter.explore_ranker.thriftscala.ProductContext
|
|
import com.twitter.finagle.stats.StatsReceiver
|
|
import com.twitter.frigate.common.base.CandidateSource
|
|
import com.twitter.frigate.common.base.CandidateSourceEligible
|
|
import com.twitter.frigate.common.base.OutOfNetworkTweetCandidate
|
|
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
|
|
import com.twitter.frigate.pushservice.model.PushTypes.Target
|
|
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
|
|
import com.twitter.frigate.pushservice.util.AdaptorUtils
|
|
import com.twitter.frigate.pushservice.util.MediaCRT
|
|
import com.twitter.frigate.pushservice.util.PushAdaptorUtil
|
|
import com.twitter.frigate.pushservice.util.PushDeviceUtil
|
|
import com.twitter.frigate.thriftscala.CommonRecommendationType
|
|
import com.twitter.product_mixer.core.thriftscala.ClientContext
|
|
import com.twitter.stitch.tweetypie.TweetyPie.TweetyPieResult
|
|
import com.twitter.storehaus.ReadableStore
|
|
import com.twitter.util.Future
|
|
|
|
case class ExploreVideoTweetCandidateAdaptor(
|
|
exploreRankerStore: ReadableStore[ExploreRankerRequest, ExploreRankerResponse],
|
|
tweetyPieStore: ReadableStore[Long, TweetyPieResult],
|
|
globalStats: StatsReceiver)
|
|
extends CandidateSource[Target, RawCandidate]
|
|
with CandidateSourceEligible[Target, RawCandidate] {
|
|
|
|
override def name: String = this.getClass.getSimpleName
|
|
private[this] val stats = globalStats.scope("ExploreVideoTweetCandidateAdaptor")
|
|
private[this] val totalInputRecs = stats.stat("input_recs")
|
|
private[this] val totalRequests = stats.counter("total_requests")
|
|
private[this] val totalEmptyResponse = stats.counter("total_empty_response")
|
|
|
|
private def buildExploreRankerRequest(
|
|
target: Target,
|
|
countryCode: Option[String],
|
|
language: Option[String],
|
|
): ExploreRankerRequest = {
|
|
ExploreRankerRequest(
|
|
clientContext = ClientContext(
|
|
userId = Some(target.targetId),
|
|
countryCode = countryCode,
|
|
languageCode = language,
|
|
),
|
|
product = Product.NotificationsVideoRecs,
|
|
productContext = Some(ProductContext.NotificationsVideoRecs(NotificationsVideoRecs())),
|
|
maxResults = Some(target.params(PushFeatureSwitchParams.MaxExploreVideoTweets))
|
|
)
|
|
}
|
|
|
|
override def get(target: Target): Future[Option[Seq[RawCandidate]]] = {
|
|
Future
|
|
.join(
|
|
target.countryCode,
|
|
target.inferredUserDeviceLanguage
|
|
).flatMap {
|
|
case (countryCode, language) =>
|
|
val request = buildExploreRankerRequest(target, countryCode, language)
|
|
exploreRankerStore.get(request).flatMap {
|
|
case Some(response) =>
|
|
val exploreResonseTweetIds = response match {
|
|
case ExploreRankerResponse(ExploreRankerProductResponse
|
|
.ImmersiveRecsResponse(ImmersiveRecsResponse(immersiveRecsResult))) =>
|
|
immersiveRecsResult.collect {
|
|
case ImmersiveRecsResult(ExploreRecommendation
|
|
.ExploreTweetRecommendation(exploreTweetRecommendation)) =>
|
|
exploreTweetRecommendation.tweetId
|
|
}
|
|
case _ =>
|
|
Seq.empty
|
|
}
|
|
|
|
totalInputRecs.add(exploreResonseTweetIds.size)
|
|
totalRequests.incr()
|
|
AdaptorUtils
|
|
.getTweetyPieResults(exploreResonseTweetIds.toSet, tweetyPieStore).map {
|
|
tweetyPieResultMap =>
|
|
val candidates = tweetyPieResultMap.values.flatten
|
|
.map(buildVideoRawCandidates(target, _))
|
|
Some(candidates.toSeq)
|
|
}
|
|
case _ =>
|
|
totalEmptyResponse.incr()
|
|
Future.None
|
|
}
|
|
case _ =>
|
|
Future.None
|
|
}
|
|
}
|
|
|
|
override def isCandidateSourceAvailable(target: Target): Future[Boolean] = {
|
|
PushDeviceUtil.isRecommendationsEligible(target).map { userRecommendationsEligible =>
|
|
userRecommendationsEligible && target.params(PushFeatureSwitchParams.EnableExploreVideoTweets)
|
|
}
|
|
}
|
|
private def buildVideoRawCandidates(
|
|
target: Target,
|
|
tweetyPieResult: TweetyPieResult
|
|
): RawCandidate with OutOfNetworkTweetCandidate = {
|
|
PushAdaptorUtil.generateOutOfNetworkTweetCandidates(
|
|
inputTarget = target,
|
|
id = tweetyPieResult.tweet.id,
|
|
mediaCRT = MediaCRT(
|
|
CommonRecommendationType.ExploreVideoTweet,
|
|
CommonRecommendationType.ExploreVideoTweet,
|
|
CommonRecommendationType.ExploreVideoTweet
|
|
),
|
|
result = Some(tweetyPieResult),
|
|
localizedEntity = None
|
|
)
|
|
}
|
|
}
|