[docx] split commit for file 3600

Signed-off-by: Ari Archer <ari.web.xyz@gmail.com>
This commit is contained in:
Ari Archer 2024-01-23 19:13:34 +02:00
parent 9ebf058832
commit ce29360463
No known key found for this signature in database
GPG Key ID: A50D5B4B599AF8A2
400 changed files with 0 additions and 17736 deletions

View File

@ -1,87 +0,0 @@
package com.twitter.frigate.pushservice.predicate.ntab_caret_fatigue
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.predicate.FatiguePredicate
import com.twitter.frigate.pushservice.predicate.CaretFeedbackHistoryFilter
import com.twitter.frigate.pushservice.predicate.{
TargetNtabCaretClickFatiguePredicate => CommonNtabCaretClickFatiguePredicate
}
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.params.PushParams
import com.twitter.frigate.thriftscala.NotificationDisplayLocation
import com.twitter.frigate.thriftscala.{CommonRecommendationType => CRT}
import com.twitter.hermit.predicate.NamedPredicate
import com.twitter.hermit.predicate.Predicate
import com.twitter.notificationservice.thriftscala.CaretFeedbackDetails
import com.twitter.util.Duration
import com.twitter.util.Future
object RecTypeNtabCaretClickFatiguePredicate {
val defaultName = "RecTypeNtabCaretClickFatiguePredicateForCandidate"
private def candidateFatiguePredicate(
genericTypeCategories: Seq[String],
crts: Set[CRT]
)(
implicit stats: StatsReceiver
): NamedPredicate[
PushCandidate
] = {
val name = "f1TriggeredCRTBasedFatiguePredciate"
val scopedStats = stats.scope(s"predicate_$name")
Predicate
.fromAsync { candidate: PushCandidate =>
if (candidate.frigateNotification.notificationDisplayLocation == NotificationDisplayLocation.PushToMobileDevice) {
if (candidate.target.params(PushParams.EnableFatigueNtabCaretClickingParam)) {
NtabCaretClickContFnFatiguePredicate
.ntabCaretClickContFnFatiguePredicates(
filterHistory = FatiguePredicate.recTypesOnlyFilter(crts),
filterCaretFeedbackHistory =
CaretFeedbackHistoryFilter.caretFeedbackHistoryFilter(genericTypeCategories),
filterInlineFeedbackHistory =
NtabCaretClickFatigueUtils.feedbackModelFilterByCRT(crts)
).apply(Seq(candidate))
.map(_.headOption.getOrElse(false))
} else Future.True
} else {
Future.True
}
}.withStats(scopedStats)
.withName(name)
}
def apply(
genericTypeCategories: Seq[String],
crts: Set[CRT],
calculateFatiguePeriod: Seq[CaretFeedbackDetails] => Duration,
useMostRecentDislikeTime: Boolean,
name: String = defaultName
)(
implicit globalStats: StatsReceiver
): NamedPredicate[PushCandidate] = {
val scopedStats = globalStats.scope(name)
val commonNtabCaretClickFatiguePredicate = CommonNtabCaretClickFatiguePredicate(
filterCaretFeedbackHistory =
CaretFeedbackHistoryFilter.caretFeedbackHistoryFilter(genericTypeCategories),
filterHistory = FatiguePredicate.recTypesOnlyFilter(crts),
calculateFatiguePeriod = calculateFatiguePeriod,
useMostRecentDislikeTime = useMostRecentDislikeTime,
name = name
)(globalStats)
Predicate
.fromAsync { candidate: PushCandidate =>
if (candidate.frigateNotification.notificationDisplayLocation == NotificationDisplayLocation.PushToMobileDevice) {
if (candidate.target.params(PushParams.EnableFatigueNtabCaretClickingParam)) {
commonNtabCaretClickFatiguePredicate
.apply(Seq(candidate.target))
.map(_.headOption.getOrElse(false))
} else Future.True
} else {
Future.True
}
}.andThen(candidateFatiguePredicate(genericTypeCategories, crts))
.withStats(scopedStats)
.withName(name)
}
}

View File

@ -1,44 +0,0 @@
package com.twitter.frigate.pushservice
import com.twitter.frigate.common.base.Candidate
import com.twitter.frigate.common.base.SocialGraphServiceRelationshipMap
import com.twitter.frigate.common.base.TweetAuthor
import com.twitter.frigate.common.rec_types.RecTypes.isInNetworkTweetType
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.hermit.predicate.Predicate
package object predicate {
implicit class CandidatesWithAuthorFollowPredicates(
predicate: Predicate[
PushCandidate with TweetAuthor with SocialGraphServiceRelationshipMap
]) {
def applyOnlyToAuthorBeingFollowPredicates: Predicate[Candidate] =
predicate.optionalOn[Candidate](
{
case candidate: PushCandidate with TweetAuthor with SocialGraphServiceRelationshipMap
if isInNetworkTweetType(candidate.commonRecType) =>
Some(candidate)
case _ =>
None
},
missingResult = true
)
}
implicit class TweetCandidateWithTweetAuthor(
predicate: Predicate[
PushCandidate with TweetAuthor with SocialGraphServiceRelationshipMap
]) {
def applyOnlyToBasicTweetPredicates: Predicate[Candidate] =
predicate.optionalOn[Candidate](
{
case candidate: PushCandidate with TweetAuthor with SocialGraphServiceRelationshipMap
if isInNetworkTweetType(candidate.commonRecType) =>
Some(candidate)
case _ =>
None
},
missingResult = true
)
}
}

View File

@ -1,27 +0,0 @@
package com.twitter.frigate.pushservice.predicate.quality_model_predicate
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.util.Future
object ExplicitOONCFilterPredicate extends QualityPredicateBase {
override lazy val name = "open_or_ntab_click_explicit_threshold"
override lazy val thresholdExtractor = (t: Target) =>
Future.value(t.params(PushFeatureSwitchParams.QualityPredicateExplicitThresholdParam))
override def scoreExtractor = (candidate: PushCandidate) =>
candidate.mrWeightedOpenOrNtabClickRankingProbability
}
object WeightedOpenOrNtabClickQualityPredicate extends QualityPredicateBase {
override lazy val name = "weighted_open_or_ntab_click_model"
override lazy val thresholdExtractor = (t: Target) => {
Future.value(0.0)
}
override def scoreExtractor =
(candidate: PushCandidate) => candidate.mrWeightedOpenOrNtabClickFilteringProbability
}

View File

@ -1,165 +0,0 @@
package com.twitter.frigate.pushservice.predicate.quality_model_predicate
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.pushservice.model.PushTypes
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.target.TargetScoringDetails
import com.twitter.hermit.predicate.NamedPredicate
import com.twitter.hermit.predicate.Predicate
import com.twitter.util.Future
object PDauCohort extends Enumeration {
type PDauCohort = Value
val cohort1 = Value
val cohort2 = Value
val cohort3 = Value
val cohort4 = Value
val cohort5 = Value
val cohort6 = Value
}
object PDauCohortUtil {
case class DauThreshold(
threshold1: Double,
threshold2: Double,
threshold3: Double,
threshold4: Double,
threshold5: Double)
val defaultDAUProb = 0.0
val dauProbThresholds = DauThreshold(
threshold1 = 0.05,
threshold2 = 0.14,
threshold3 = 0.33,
threshold4 = 0.7,
threshold5 = 0.959
)
val finerThresholdMap =
Map(
PDauCohort.cohort2 -> List(0.05, 0.0539, 0.0563, 0.0600, 0.0681, 0.0733, 0.0800, 0.0849,
0.0912, 0.0975, 0.1032, 0.1092, 0.1134, 0.1191, 0.1252, 0.1324, 0.14),
PDauCohort.cohort3 -> List(0.14, 0.1489, 0.1544, 0.1625, 0.1704, 0.1797, 0.1905, 0.2001,
0.2120, 0.2248, 0.2363, 0.2500, 0.2650, 0.2801, 0.2958, 0.3119, 0.33),
PDauCohort.cohort4 -> List(0.33, 0.3484, 0.3686, 0.3893, 0.4126, 0.4350, 0.4603, 0.4856,
0.5092, 0.5348, 0.5602, 0.5850, 0.6087, 0.6319, 0.6548, 0.6779, 0.7),
PDauCohort.cohort5 -> List(0.7, 0.7295, 0.7581, 0.7831, 0.8049, 0.8251, 0.8444, 0.8612,
0.8786, 0.8936, 0.9043, 0.9175, 0.9290, 0.9383, 0.9498, 0.9587, 0.959)
)
def getBucket(targetUser: PushTypes.Target, doImpression: Boolean) = {
implicit val stats = targetUser.stats.scope("PDauCohortUtil")
if (doImpression) targetUser.getBucket _ else targetUser.getBucketWithoutImpression _
}
def threshold1(targetUser: PushTypes.Target): Double = dauProbThresholds.threshold1
def threshold2(targetUser: PushTypes.Target): Double = dauProbThresholds.threshold2
def threshold3(targetUser: PushTypes.Target): Double = dauProbThresholds.threshold3
def threshold4(targetUser: PushTypes.Target): Double = dauProbThresholds.threshold4
def threshold5(targetUser: PushTypes.Target): Double = dauProbThresholds.threshold5
def thresholdForCohort(targetUser: PushTypes.Target, dauCohort: Int): Double = {
if (dauCohort == 0) 0.0
else if (dauCohort == 1) threshold1(targetUser)
else if (dauCohort == 2) threshold2(targetUser)
else if (dauCohort == 3) threshold3(targetUser)
else if (dauCohort == 4) threshold4(targetUser)
else if (dauCohort == 5) threshold5(targetUser)
else 1.0
}
def getPDauCohort(dauProbability: Double, thresholds: DauThreshold): PDauCohort.Value = {
dauProbability match {
case dauProb if dauProb >= 0.0 && dauProb < thresholds.threshold1 => PDauCohort.cohort1
case dauProb if dauProb >= thresholds.threshold1 && dauProb < thresholds.threshold2 =>
PDauCohort.cohort2
case dauProb if dauProb >= thresholds.threshold2 && dauProb < thresholds.threshold3 =>
PDauCohort.cohort3
case dauProb if dauProb >= thresholds.threshold3 && dauProb < thresholds.threshold4 =>
PDauCohort.cohort4
case dauProb if dauProb >= thresholds.threshold4 && dauProb < thresholds.threshold5 =>
PDauCohort.cohort5
case dauProb if dauProb >= thresholds.threshold5 && dauProb <= 1.0 => PDauCohort.cohort6
}
}
def getDauProb(target: TargetScoringDetails): Future[Double] = {
target.dauProbability.map { dauProb =>
dauProb.map(_.probability).getOrElse(defaultDAUProb)
}
}
def getPDauCohort(target: TargetScoringDetails): Future[PDauCohort.Value] = {
getDauProb(target).map { getPDauCohort(_, dauProbThresholds) }
}
def getPDauCohortWithPDau(target: TargetScoringDetails): Future[(PDauCohort.Value, Double)] = {
getDauProb(target).map { prob =>
(getPDauCohort(prob, dauProbThresholds), prob)
}
}
def updateStats(
target: PushTypes.Target,
modelName: String,
predicateResult: Boolean
)(
implicit statsReceiver: StatsReceiver
): Unit = {
val dauCohortOp = getPDauCohort(target)
dauCohortOp.map { dauCohort =>
val cohortStats = statsReceiver.scope(modelName).scope(dauCohort.toString)
cohortStats.counter(s"filter_$predicateResult").incr()
}
if (target.isNewSignup) {
val newUserModelStats = statsReceiver.scope(modelName)
newUserModelStats.counter(s"new_user_filter_$predicateResult").incr()
}
}
}
trait QualityPredicateBase {
def name: String
def thresholdExtractor: Target => Future[Double]
def scoreExtractor: PushCandidate => Future[Option[Double]]
def isPredicateEnabled: PushCandidate => Future[Boolean] = _ => Future.True
def comparator: (Double, Double) => Boolean =
(score: Double, threshold: Double) => score >= threshold
def updateCustomStats(
candidate: PushCandidate,
score: Double,
threshold: Double,
result: Boolean
)(
implicit statsReceiver: StatsReceiver
): Unit = {}
def apply()(implicit statsReceiver: StatsReceiver): NamedPredicate[PushCandidate] = {
Predicate
.fromAsync { candidate: PushCandidate =>
isPredicateEnabled(candidate).flatMap {
case true =>
scoreExtractor(candidate).flatMap { scoreOpt =>
thresholdExtractor(candidate.target).map { threshold =>
val score = scoreOpt.getOrElse(0.0)
val result = comparator(score, threshold)
PDauCohortUtil.updateStats(candidate.target, name, result)
updateCustomStats(candidate, score, threshold, result)
result
}
}
case _ => Future.True
}
}
.withStats(statsReceiver.scope(s"predicate_$name"))
.withName(name)
}
}

View File

@ -1,21 +0,0 @@
package com.twitter.frigate.pushservice.predicate.quality_model_predicate
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.params.QualityPredicateEnum
import com.twitter.frigate.pushservice.predicate.PredicatesForCandidate
import com.twitter.hermit.predicate.NamedPredicate
object QualityPredicateMap {
def apply(
)(
implicit statsReceiver: StatsReceiver
): Map[QualityPredicateEnum.Value, NamedPredicate[PushCandidate]] = {
Map(
QualityPredicateEnum.WeightedOpenOrNtabClick -> WeightedOpenOrNtabClickQualityPredicate(),
QualityPredicateEnum.ExplicitOpenOrNtabClickFilter -> ExplicitOONCFilterPredicate(),
QualityPredicateEnum.AlwaysTrue -> PredicatesForCandidate.alwaysTruePushCandidatePredicate,
)
}
}

View File

@ -1,54 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.thriftscala.CommonRecommendationType
/**
* This Ranker re-ranks MR candidates, boosting input CRTs.
* Relative ranking between input CRTs and rest of the candidates doesn't change
*
* Ex: T: Tweet candidate, F: input CRT candidatess
*
* T3, F2, T1, T2, F1 => F2, F1, T3, T1, T2
*/
case class CRTBoostRanker(statsReceiver: StatsReceiver) {
private val recsToBoostStat = statsReceiver.stat("recs_to_boost")
private val otherRecsStat = statsReceiver.stat("other_recs")
private def boostCrtToTop(
inputCandidates: Seq[CandidateDetails[PushCandidate]],
crtToBoost: CommonRecommendationType
): Seq[CandidateDetails[PushCandidate]] = {
val (upRankedCandidates, otherCandidates) =
inputCandidates.partition(_.candidate.commonRecType == crtToBoost)
recsToBoostStat.add(upRankedCandidates.size)
otherRecsStat.add(otherCandidates.size)
upRankedCandidates ++ otherCandidates
}
final def boostCrtsToTop(
inputCandidates: Seq[CandidateDetails[PushCandidate]],
crtsToBoost: Seq[CommonRecommendationType]
): Seq[CandidateDetails[PushCandidate]] = {
crtsToBoost.headOption match {
case Some(crt) =>
val upRankedCandidates = boostCrtToTop(inputCandidates, crt)
boostCrtsToTop(upRankedCandidates, crtsToBoost.tail)
case None => inputCandidates
}
}
final def boostCrtsToTopStableOrder(
inputCandidates: Seq[CandidateDetails[PushCandidate]],
crtsToBoost: Seq[CommonRecommendationType]
): Seq[CandidateDetails[PushCandidate]] = {
val crtsToBoostSet = crtsToBoost.toSet
val (upRankedCandidates, otherCandidates) = inputCandidates.partition(candidateDetail =>
crtsToBoostSet.contains(candidateDetail.candidate.commonRecType))
upRankedCandidates ++ otherCandidates
}
}

View File

@ -1,45 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.thriftscala.CommonRecommendationType
/**
* This Ranker re-ranks MR candidates, down ranks input CRTs.
* Relative ranking between input CRTs and rest of the candidates doesn't change
*
* Ex: T: Tweet candidate, F: input CRT candidates
*
* T3, F2, T1, T2, F1 => T3, T1, T2, F2, F1
*/
case class CRTDownRanker(statsReceiver: StatsReceiver) {
private val recsToDownRankStat = statsReceiver.stat("recs_to_down_rank")
private val otherRecsStat = statsReceiver.stat("other_recs")
private val downRankerRequests = statsReceiver.counter("down_ranker_requests")
private def downRank(
inputCandidates: Seq[CandidateDetails[PushCandidate]],
crtToDownRank: CommonRecommendationType
): Seq[CandidateDetails[PushCandidate]] = {
downRankerRequests.incr()
val (downRankedCandidates, otherCandidates) =
inputCandidates.partition(_.candidate.commonRecType == crtToDownRank)
recsToDownRankStat.add(downRankedCandidates.size)
otherRecsStat.add(otherCandidates.size)
otherCandidates ++ downRankedCandidates
}
final def downRank(
inputCandidates: Seq[CandidateDetails[PushCandidate]],
crtsToDownRank: Seq[CommonRecommendationType]
): Seq[CandidateDetails[PushCandidate]] = {
crtsToDownRank.headOption match {
case Some(crt) =>
val downRankedCandidates = downRank(inputCandidates, crt)
downRank(downRankedCandidates, crtsToDownRank.tail)
case None => inputCandidates
}
}
}

View File

@ -1,45 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.TweetCandidate
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.stitch.tweetypie.TweetyPie.TweetyPieResult
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
class LoggedOutRanker(tweetyPieStore: ReadableStore[Long, TweetyPieResult], stats: StatsReceiver) {
private val statsReceiver = stats.scope(this.getClass.getSimpleName)
private val rankedCandidates = statsReceiver.counter("ranked_candidates_count")
def rank(
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val tweetIds = candidates.map { cand => cand.candidate.asInstanceOf[TweetCandidate].tweetId }
val results = tweetyPieStore.multiGet(tweetIds.toSet).values.toSeq
val futureOfResults = Future.traverseSequentially(results)(r => r)
val tweetsFut = futureOfResults.map { tweetyPieResults =>
tweetyPieResults.map(_.map(_.tweet))
}
val sortedTweetsFuture = tweetsFut.map { tweets =>
tweets
.map { tweet =>
if (tweet.isDefined && tweet.get.counts.isDefined) {
tweet.get.id -> tweet.get.counts.get.favoriteCount.getOrElse(0L)
} else {
0 -> 0L
}
}.sortBy(_._2)(Ordering[Long].reverse)
}
val finalCandidates = sortedTweetsFuture.map { sortedTweets =>
sortedTweets
.map { tweet =>
candidates.find(_.candidate.asInstanceOf[TweetCandidate].tweetId == tweet._1).orNull
}.filter { cand => cand != null }
}
finalCandidates.map { fc =>
rankedCandidates.incr(fc.size)
}
finalCandidates
}
}

View File

@ -1,204 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.util.Future
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.pushservice.params.MrQualityUprankingPartialTypeEnum
import com.twitter.frigate.common.base.TweetCandidate
import com.twitter.frigate.common.rec_types.RecTypes
import com.twitter.frigate.pushservice.params.PushConstants.OoncQualityCombinedScore
object ModelBasedRanker {
def rankBySpecifiedScore(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
scoreExtractor: PushCandidate => Future[Option[Double]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val scoredCandidatesFutures = candidatesDetails.map { cand =>
scoreExtractor(cand.candidate).map { scoreOp => (cand, scoreOp.getOrElse(0.0)) }
}
Future.collect(scoredCandidatesFutures).map { scores =>
val sorted = scores.sortBy { candidateDetails => -1 * candidateDetails._2 }
sorted.map(_._1)
}
}
def populatePredictionScoreStats(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
scoreExtractor: PushCandidate => Future[Option[Double]],
predictionScoreStats: StatsReceiver
): Unit = {
val scoreScaleFactorForStat = 10000
val statName = "prediction_scores"
candidatesDetails.map {
case CandidateDetails(candidate, source) =>
val crt = candidate.commonRecType
scoreExtractor(candidate).map { scoreOp =>
val scaledScore = (scoreOp.getOrElse(0.0) * scoreScaleFactorForStat).toFloat
predictionScoreStats.scope("all_candidates").stat(statName).add(scaledScore)
predictionScoreStats.scope(crt.toString()).stat(statName).add(scaledScore)
}
}
}
def populateMrWeightedOpenOrNtabClickScoreStats(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
predictionScoreStats: StatsReceiver
): Unit = {
populatePredictionScoreStats(
candidatesDetails,
candidate => candidate.mrWeightedOpenOrNtabClickRankingProbability,
predictionScoreStats
)
}
def populateMrQualityUprankingScoreStats(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
predictionScoreStats: StatsReceiver
): Unit = {
populatePredictionScoreStats(
candidatesDetails,
candidate => candidate.mrQualityUprankingProbability,
predictionScoreStats
)
}
def rankByMrWeightedOpenOrNtabClickScore(
candidatesDetails: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
rankBySpecifiedScore(
candidatesDetails,
candidate => candidate.mrWeightedOpenOrNtabClickRankingProbability
)
}
def transformSigmoid(
score: Double,
weight: Double = 1.0,
bias: Double = 0.0
): Double = {
val base = -1.0 * (weight * score + bias)
val cappedBase = math.max(math.min(base, 100.0), -100.0)
1.0 / (1.0 + math.exp(cappedBase))
}
def transformLinear(
score: Double,
bar: Double = 1.0
): Double = {
val positiveBar = math.abs(bar)
val cappedScore = math.max(math.min(score, positiveBar), -1.0 * positiveBar)
cappedScore / positiveBar
}
def transformIdentity(
score: Double
): Double = score
def rankByQualityOoncCombinedScore(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
qualityScoreTransform: Double => Double,
qualityScoreBoost: Double = 1.0
): Future[Seq[CandidateDetails[PushCandidate]]] = {
rankBySpecifiedScore(
candidatesDetails,
candidate => {
val ooncScoreFutOpt: Future[Option[Double]] =
candidate.mrWeightedOpenOrNtabClickRankingProbability
val qualityScoreFutOpt: Future[Option[Double]] =
candidate.mrQualityUprankingProbability
Future
.join(
ooncScoreFutOpt,
qualityScoreFutOpt
).map {
case (Some(ooncScore), Some(qualityScore)) =>
val transformedQualityScore = qualityScoreTransform(qualityScore)
val combinedScore = ooncScore * (1.0 + qualityScoreBoost * transformedQualityScore)
candidate
.cacheExternalScore(OoncQualityCombinedScore, Future.value(Some(combinedScore)))
Some(combinedScore)
case _ => None
}
}
)
}
def rerankByProducerQualityOoncCombinedScore(
candidateDetails: Seq[CandidateDetails[PushCandidate]]
)(
implicit stat: StatsReceiver
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val scopedStat = stat.scope("producer_quality_reranking")
val oonCandidates = candidateDetails.filter {
case CandidateDetails(pushCandidate: PushCandidate, _) =>
tweetCandidateSelector(pushCandidate, MrQualityUprankingPartialTypeEnum.Oon)
}
val rankedOonCandidatesFut = rankBySpecifiedScore(
oonCandidates,
candidate => {
val baseScoreFutureOpt: Future[Option[Double]] = {
val qualityCombinedScoreFutureOpt =
candidate.getExternalCachedScoreByName(OoncQualityCombinedScore)
val ooncScoreFutureOpt = candidate.mrWeightedOpenOrNtabClickRankingProbability
Future.join(qualityCombinedScoreFutureOpt, ooncScoreFutureOpt).map {
case (Some(qualityCombinedScore), _) =>
scopedStat.counter("quality_combined_score").incr()
Some(qualityCombinedScore)
case (_, ooncScoreOpt) =>
scopedStat.counter("oonc_score").incr()
ooncScoreOpt
}
}
baseScoreFutureOpt.map {
case Some(baseScore) =>
val boostRatio = candidate.mrProducerQualityUprankingBoost.getOrElse(1.0)
if (boostRatio > 1.0) scopedStat.counter("author_uprank").incr()
else if (boostRatio < 1.0) scopedStat.counter("author_downrank").incr()
else scopedStat.counter("author_noboost").incr()
Some(baseScore * boostRatio)
case _ =>
scopedStat.counter("empty_score").incr()
None
}
}
)
rankedOonCandidatesFut.map { rankedOonCandidates =>
val sortedOonCandidateIterator = rankedOonCandidates.toIterator
candidateDetails.map { ooncRankedCandidate =>
val isOon = tweetCandidateSelector(
ooncRankedCandidate.candidate,
MrQualityUprankingPartialTypeEnum.Oon)
if (sortedOonCandidateIterator.hasNext && isOon)
sortedOonCandidateIterator.next()
else ooncRankedCandidate
}
}
}
def tweetCandidateSelector(
pushCandidate: PushCandidate,
selectedCandidateType: MrQualityUprankingPartialTypeEnum.Value
): Boolean = {
pushCandidate match {
case candidate: PushCandidate with TweetCandidate =>
selectedCandidateType match {
case MrQualityUprankingPartialTypeEnum.Oon =>
val crt = candidate.commonRecType
RecTypes.isOutOfNetworkTweetRecType(crt) || RecTypes.outOfNetworkTopicTweetTypes
.contains(crt)
case _ => true
}
case _ => false
}
}
}

View File

@ -1,31 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.Ranker
import com.twitter.util.Future
trait PushserviceRanker[T, C] extends Ranker[T, C] {
/**
* Initial Ranking of input candidates
*/
def initialRank(target: T, candidates: Seq[CandidateDetails[C]]): Future[Seq[CandidateDetails[C]]]
/**
* Re-ranks input ranked candidates. Useful when a subset of candidates are ranked
* by a different logic, while preserving the initial ranking for the rest
*/
def reRank(
target: T,
rankedCandidates: Seq[CandidateDetails[C]]
): Future[Seq[CandidateDetails[C]]]
/**
* Final ranking that does Initial + Rerank
*/
override final def rank(target: T, candidates: Seq[CandidateDetails[C]]): (
Future[Seq[CandidateDetails[C]]]
) = {
initialRank(target, candidates).flatMap { rankedCandidates => reRank(target, rankedCandidates) }
}
}

View File

@ -1,139 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.contentrecommender.thriftscala.LightRankingCandidate
import com.twitter.contentrecommender.thriftscala.LightRankingFeatureHydrationContext
import com.twitter.contentrecommender.thriftscala.MagicRecsFeatureHydrationContext
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.RandomRanker
import com.twitter.frigate.common.base.Ranker
import com.twitter.frigate.common.base.TweetAuthor
import com.twitter.frigate.common.base.TweetCandidate
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.params.PushConstants
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.frigate.pushservice.params.PushParams
import com.twitter.ml.featurestore.lib.UserId
import com.twitter.nrel.lightranker.MagicRecsServeDataRecordLightRanker
import com.twitter.util.Future
class RFPHLightRanker(
lightRanker: MagicRecsServeDataRecordLightRanker,
stats: StatsReceiver)
extends Ranker[Target, PushCandidate] {
private val statsReceiver = stats.scope(this.getClass.getSimpleName)
private val lightRankerCandidateCounter = statsReceiver.counter("light_ranker_candidate_count")
private val lightRankerRequestCounter = statsReceiver.counter("light_ranker_request_count")
private val lightRankingStats: StatsReceiver = statsReceiver.scope("light_ranking")
private val restrictLightRankingCounter: Counter =
lightRankingStats.counter("restrict_light_ranking")
private val selectedLightRankerScribedTargetCandidateCountStats: Stat =
lightRankingStats.stat("selected_light_ranker_scribed_target_candidate_count")
private val selectedLightRankerScribedCandidatesStats: Stat =
lightRankingStats.stat("selected_light_ranker_scribed_candidates")
private val lightRankingRandomBaselineStats: StatsReceiver =
statsReceiver.scope("light_ranking_random_baseline")
override def rank(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val enableLightRanker = target.params(PushFeatureSwitchParams.EnableLightRankingParam)
val restrictLightRanker = target.params(PushParams.RestrictLightRankingParam)
val lightRankerSelectionThreshold =
target.params(PushFeatureSwitchParams.LightRankingNumberOfCandidatesParam)
val randomRanker = RandomRanker[Target, PushCandidate]()(lightRankingRandomBaselineStats)
if (enableLightRanker && candidates.length > lightRankerSelectionThreshold && !target.scribeFeatureForRequestScribe) {
val (tweetCandidates, nonTweetCandidates) =
candidates.partition {
case CandidateDetails(pushCandidate: PushCandidate with TweetCandidate, source) => true
case _ => false
}
val lightRankerSelectedTweetCandidatesFut = {
if (restrictLightRanker) {
restrictLightRankingCounter.incr()
lightRankThenTake(
target,
tweetCandidates
.asInstanceOf[Seq[CandidateDetails[PushCandidate with TweetCandidate]]],
PushConstants.RestrictLightRankingCandidatesThreshold
)
} else if (target.params(PushFeatureSwitchParams.EnableRandomBaselineLightRankingParam)) {
randomRanker.rank(target, tweetCandidates).map { randomLightRankerCands =>
randomLightRankerCands.take(lightRankerSelectionThreshold)
}
} else {
lightRankThenTake(
target,
tweetCandidates
.asInstanceOf[Seq[CandidateDetails[PushCandidate with TweetCandidate]]],
lightRankerSelectionThreshold
)
}
}
lightRankerSelectedTweetCandidatesFut.map { returnedTweetCandidates =>
nonTweetCandidates ++ returnedTweetCandidates
}
} else if (target.scribeFeatureForRequestScribe) {
val downSampleRate: Double =
if (target.params(PushParams.DownSampleLightRankingScribeCandidatesParam))
PushConstants.DownSampleLightRankingScribeCandidatesRate
else target.params(PushFeatureSwitchParams.LightRankingScribeCandidatesDownSamplingParam)
val selectedCandidateCounter: Int = math.ceil(candidates.size * downSampleRate).toInt
selectedLightRankerScribedTargetCandidateCountStats.add(selectedCandidateCounter.toFloat)
randomRanker.rank(target, candidates).map { randomLightRankerCands =>
val selectedCandidates = randomLightRankerCands.take(selectedCandidateCounter)
selectedLightRankerScribedCandidatesStats.add(selectedCandidates.size.toFloat)
selectedCandidates
}
} else Future.value(candidates)
}
private def lightRankThenTake(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate with TweetCandidate]],
numOfCandidates: Int
): Future[Seq[CandidateDetails[PushCandidate]]] = {
lightRankerCandidateCounter.incr(candidates.length)
lightRankerRequestCounter.incr()
val lightRankerCandidates: Seq[LightRankingCandidate] = candidates.map {
case CandidateDetails(tweetCandidate, _) =>
val tweetAuthor = tweetCandidate match {
case t: TweetCandidate with TweetAuthor => t.authorId
case _ => None
}
val hydrationContext: LightRankingFeatureHydrationContext =
LightRankingFeatureHydrationContext.MagicRecsHydrationContext(
MagicRecsFeatureHydrationContext(
tweetAuthor = tweetAuthor,
pushString = tweetCandidate.getPushCopy.flatMap(_.pushStringGroup).map(_.toString))
)
LightRankingCandidate(
tweetId = tweetCandidate.tweetId,
hydrationContext = Some(hydrationContext)
)
}
val modelName = target.params(PushFeatureSwitchParams.LightRankingModelTypeParam)
val lightRankedCandidatesFut = {
lightRanker
.rank(UserId(target.targetId), lightRankerCandidates, modelName)
}
lightRankedCandidatesFut.map { lightRankedCandidates =>
val lrScoreMap = lightRankedCandidates.map { lrCand =>
lrCand.tweetId -> lrCand.score
}.toMap
val candScoreMap: Seq[Option[Double]] = candidates.map { candidateDetails =>
lrScoreMap.get(candidateDetails.candidate.tweetId)
}
sortCandidatesByScore(candidates, candScoreMap)
.take(numOfCandidates)
}
}
}

View File

@ -1,297 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.Ranker
import com.twitter.frigate.common.rec_types.RecTypes
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.ml.HealthFeatureGetter
import com.twitter.frigate.pushservice.ml.PushMLModelScorer
import com.twitter.frigate.pushservice.params.MrQualityUprankingPartialTypeEnum
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.frigate.pushservice.params.PushMLModel
import com.twitter.frigate.pushservice.params.PushModelName
import com.twitter.frigate.pushservice.params.PushParams
import com.twitter.frigate.pushservice.util.MediaAnnotationsUtil.updateMediaCategoryStats
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.util.Future
import com.twitter.frigate.pushservice.params.MrQualityUprankingTransformTypeEnum
import com.twitter.storehaus.ReadableStore
import com.twitter.frigate.thriftscala.UserMediaRepresentation
import com.twitter.hss.api.thriftscala.UserHealthSignalResponse
class RFPHRanker(
randomRanker: Ranker[Target, PushCandidate],
weightedOpenOrNtabClickModelScorer: PushMLModelScorer,
subscriptionCreatorRanker: SubscriptionCreatorRanker,
userHealthSignalStore: ReadableStore[Long, UserHealthSignalResponse],
producerMediaRepresentationStore: ReadableStore[Long, UserMediaRepresentation],
stats: StatsReceiver)
extends PushserviceRanker[Target, PushCandidate] {
private val statsReceiver = stats.scope(this.getClass.getSimpleName)
private val boostCRTsRanker = CRTBoostRanker(statsReceiver.scope("boost_desired_crts"))
private val crtDownRanker = CRTDownRanker(statsReceiver.scope("down_rank_desired_crts"))
private val crtsToDownRank = statsReceiver.stat("crts_to_downrank")
private val crtsToUprank = statsReceiver.stat("crts_to_uprank")
private val randomRankingCounter = stats.counter("randomRanking")
private val mlRankingCounter = stats.counter("mlRanking")
private val disableAllRelevanceCounter = stats.counter("disableAllRelevance")
private val disableHeavyRankingCounter = stats.counter("disableHeavyRanking")
private val heavyRankerCandidateCounter = stats.counter("heavy_ranker_candidate_count")
private val heavyRankerScoreStats = statsReceiver.scope("heavy_ranker_prediction_scores")
private val producerUprankingCounter = statsReceiver.counter("producer_quality_upranking")
private val producerBoostedCounter = statsReceiver.counter("producer_boosted_candidates")
private val producerDownboostedCounter = statsReceiver.counter("producer_downboosted_candidates")
override def initialRank(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
heavyRankerCandidateCounter.incr(candidates.size)
updateMediaCategoryStats(candidates)(stats)
target.targetUserState
.flatMap { targetUserState =>
val useRandomRanking = target.skipMlRanker || target.params(
PushParams.UseRandomRankingParam
)
if (useRandomRanking) {
randomRankingCounter.incr()
randomRanker.rank(target, candidates)
} else if (target.params(PushParams.DisableAllRelevanceParam)) {
disableAllRelevanceCounter.incr()
Future.value(candidates)
} else if (target.params(PushParams.DisableHeavyRankingParam) || target.params(
PushFeatureSwitchParams.DisableHeavyRankingModelFSParam)) {
disableHeavyRankingCounter.incr()
Future.value(candidates)
} else {
mlRankingCounter.incr()
val scoredCandidatesFut = scoring(target, candidates)
target.rankingModelParam.map { rankingModelParam =>
val modelName = PushModelName(
PushMLModel.WeightedOpenOrNtabClickProbability,
target.params(rankingModelParam)).toString
ModelBasedRanker.populateMrWeightedOpenOrNtabClickScoreStats(
candidates,
heavyRankerScoreStats.scope(modelName)
)
}
if (target.params(
PushFeatureSwitchParams.EnableQualityUprankingCrtScoreStatsForHeavyRankingParam)) {
val modelName = PushModelName(
PushMLModel.FilteringProbability,
target.params(PushFeatureSwitchParams.QualityUprankingModelTypeParam)
).toString
ModelBasedRanker.populateMrQualityUprankingScoreStats(
candidates,
heavyRankerScoreStats.scope(modelName)
)
}
val ooncRankedCandidatesFut =
scoredCandidatesFut.flatMap(ModelBasedRanker.rankByMrWeightedOpenOrNtabClickScore)
val qualityUprankedCandidatesFut =
if (target.params(PushFeatureSwitchParams.EnableQualityUprankingForHeavyRankingParam)) {
ooncRankedCandidatesFut.flatMap { ooncRankedCandidates =>
val transformFunc: Double => Double =
target.params(PushFeatureSwitchParams.QualityUprankingTransformTypeParam) match {
case MrQualityUprankingTransformTypeEnum.Linear =>
ModelBasedRanker.transformLinear(
_,
bar = target.params(
PushFeatureSwitchParams.QualityUprankingLinearBarForHeavyRankingParam))
case MrQualityUprankingTransformTypeEnum.Sigmoid =>
ModelBasedRanker.transformSigmoid(
_,
weight = target.params(
PushFeatureSwitchParams.QualityUprankingSigmoidWeightForHeavyRankingParam),
bias = target.params(
PushFeatureSwitchParams.QualityUprankingSigmoidBiasForHeavyRankingParam)
)
case _ => ModelBasedRanker.transformIdentity
}
ModelBasedRanker.rankByQualityOoncCombinedScore(
ooncRankedCandidates,
transformFunc,
target.params(PushFeatureSwitchParams.QualityUprankingBoostForHeavyRankingParam)
)
}
} else ooncRankedCandidatesFut
if (target.params(
PushFeatureSwitchParams.EnableProducersQualityBoostingForHeavyRankingParam)) {
producerUprankingCounter.incr()
qualityUprankedCandidatesFut.flatMap(cands =>
ModelBasedRanker.rerankByProducerQualityOoncCombinedScore(cands)(statsReceiver))
} else qualityUprankedCandidatesFut
}
}
}
private def scoring(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val ooncScoredCandidatesFut = target.rankingModelParam.map { rankingModelParam =>
weightedOpenOrNtabClickModelScorer.scoreByBatchPredictionForModelVersion(
target,
candidates,
rankingModelParam
)
}
val scoredCandidatesFut = {
if (target.params(PushFeatureSwitchParams.EnableQualityUprankingForHeavyRankingParam)) {
ooncScoredCandidatesFut.map { candidates =>
weightedOpenOrNtabClickModelScorer.scoreByBatchPredictionForModelVersion(
target = target,
candidatesDetails = candidates,
modelVersionParam = PushFeatureSwitchParams.QualityUprankingModelTypeParam,
overridePushMLModelOpt = Some(PushMLModel.FilteringProbability)
)
}
} else ooncScoredCandidatesFut
}
scoredCandidatesFut.foreach { candidates =>
val oonCandidates = candidates.filter {
case CandidateDetails(pushCandidate: PushCandidate, _) =>
ModelBasedRanker.tweetCandidateSelector(
pushCandidate,
MrQualityUprankingPartialTypeEnum.Oon)
}
setProducerQuality(
target,
oonCandidates,
userHealthSignalStore,
producerMediaRepresentationStore)
}
}
private def setProducerQuality(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]],
userHealthSignalStore: ReadableStore[Long, UserHealthSignalResponse],
producerMediaRepresentationStore: ReadableStore[Long, UserMediaRepresentation]
): Unit = {
lazy val boostRatio =
target.params(PushFeatureSwitchParams.QualityUprankingBoostForHighQualityProducersParam)
lazy val downboostRatio =
target.params(PushFeatureSwitchParams.QualityUprankingDownboostForLowQualityProducersParam)
candidates.foreach {
case CandidateDetails(pushCandidate, _) =>
HealthFeatureGetter
.getFeatures(pushCandidate, producerMediaRepresentationStore, userHealthSignalStore).map {
featureMap =>
val agathaNsfwScore = featureMap.numericFeatures.getOrElse("agathaNsfwScore", 0.5)
val textNsfwScore = featureMap.numericFeatures.getOrElse("textNsfwScore", 0.15)
val nudityRate = featureMap.numericFeatures.getOrElse("nudityRate", 0.0)
val activeFollowers = featureMap.numericFeatures.getOrElse("activeFollowers", 0.0)
val favorsRcvd28Days = featureMap.numericFeatures.getOrElse("favorsRcvd28Days", 0.0)
val tweets28Days = featureMap.numericFeatures.getOrElse("tweets28Days", 0.0)
val authorDislikeCount = featureMap.numericFeatures
.getOrElse("authorDislikeCount", 0.0)
val authorDislikeRate = featureMap.numericFeatures.getOrElse("authorDislikeRate", 0.0)
val authorReportRate = featureMap.numericFeatures.getOrElse("authorReportRate", 0.0)
val abuseStrikeTop2Percent =
featureMap.booleanFeatures.getOrElse("abuseStrikeTop2Percent", false)
val abuseStrikeTop1Percent =
featureMap.booleanFeatures.getOrElse("abuseStrikeTop1Percent", false)
val hasNsfwToken = featureMap.booleanFeatures.getOrElse("hasNsfwToken", false)
if ((activeFollowers > 3000000) ||
(activeFollowers > 1000000 && agathaNsfwScore < 0.7 && nudityRate < 0.01 && !hasNsfwToken && !abuseStrikeTop2Percent) ||
(activeFollowers > 100000 && agathaNsfwScore < 0.7 && nudityRate < 0.01 && !hasNsfwToken && !abuseStrikeTop2Percent &&
tweets28Days > 0 && favorsRcvd28Days / tweets28Days > 3000 && authorReportRate < 0.000001 && authorDislikeRate < 0.0005)) {
producerBoostedCounter.incr()
pushCandidate.setProducerQualityUprankingBoost(boostRatio)
} else if (activeFollowers < 5 || agathaNsfwScore > 0.9 || nudityRate > 0.03 || hasNsfwToken || abuseStrikeTop1Percent ||
textNsfwScore > 0.4 || (authorDislikeRate > 0.005 && authorDislikeCount > 5) ||
(tweets28Days > 56 && favorsRcvd28Days / tweets28Days < 100)) {
producerDownboostedCounter.incr()
pushCandidate.setProducerQualityUprankingBoost(downboostRatio)
} else pushCandidate.setProducerQualityUprankingBoost(1.0)
}
}
}
private def rerankBySubscriptionCreatorRanker(
target: Target,
rankedCandidates: Future[Seq[CandidateDetails[PushCandidate]]],
): Future[Seq[CandidateDetails[PushCandidate]]] = {
if (target.params(PushFeatureSwitchParams.SoftRankCandidatesFromSubscriptionCreators)) {
val factor = target.params(PushFeatureSwitchParams.SoftRankFactorForSubscriptionCreators)
subscriptionCreatorRanker.boostByScoreFactor(rankedCandidates, factor)
} else
subscriptionCreatorRanker.boostSubscriptionCreator(rankedCandidates)
}
override def reRank(
target: Target,
rankedCandidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val numberOfF1Candidates =
rankedCandidates.count(candidateDetails =>
RecTypes.isF1Type(candidateDetails.candidate.commonRecType))
lazy val threshold =
target.params(PushFeatureSwitchParams.NumberOfF1CandidatesThresholdForOONBackfill)
lazy val enableOONBackfillBasedOnF1 =
target.params(PushFeatureSwitchParams.EnableOONBackfillBasedOnF1Candidates)
val f1BoostedCandidates =
if (enableOONBackfillBasedOnF1 && numberOfF1Candidates > threshold) {
boostCRTsRanker.boostCrtsToTopStableOrder(
rankedCandidates,
RecTypes.f1FirstDegreeTypes.toSeq)
} else rankedCandidates
val topTweetsByGeoDownRankedCandidates =
if (target.params(PushFeatureSwitchParams.BackfillRankTopTweetsByGeoCandidates)) {
crtDownRanker.downRank(
f1BoostedCandidates,
Seq(CommonRecommendationType.GeoPopTweet)
)
} else f1BoostedCandidates
val reRankedCandidatesWithBoostedCrts = {
val listOfCrtsToUpRank = target
.params(PushFeatureSwitchParams.ListOfCrtsToUpRank)
.flatMap(CommonRecommendationType.valueOf)
crtsToUprank.add(listOfCrtsToUpRank.size)
boostCRTsRanker.boostCrtsToTop(topTweetsByGeoDownRankedCandidates, listOfCrtsToUpRank)
}
val reRankedCandidatesWithDownRankedCrts = {
val listOfCrtsToDownRank = target
.params(PushFeatureSwitchParams.ListOfCrtsToDownRank)
.flatMap(CommonRecommendationType.valueOf)
crtsToDownRank.add(listOfCrtsToDownRank.size)
crtDownRanker.downRank(reRankedCandidatesWithBoostedCrts, listOfCrtsToDownRank)
}
val rerankBySubscriptionCreatorFut = {
if (target.params(PushFeatureSwitchParams.BoostCandidatesFromSubscriptionCreators)) {
rerankBySubscriptionCreatorRanker(
target,
Future.value(reRankedCandidatesWithDownRankedCrts))
} else Future.value(reRankedCandidatesWithDownRankedCrts)
}
rerankBySubscriptionCreatorFut
}
}

View File

@ -1,110 +0,0 @@
package com.twitter.frigate.pushservice.rank
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.TweetAuthor
import com.twitter.frigate.common.base.TweetCandidate
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.storehaus.FutureOps
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
class SubscriptionCreatorRanker(
superFollowEligibilityUserStore: ReadableStore[Long, Boolean],
statsReceiver: StatsReceiver) {
private val scopedStats = statsReceiver.scope("SubscriptionCreatorRanker")
private val boostStats = scopedStats.scope("boostSubscriptionCreator")
private val softUprankStats = scopedStats.scope("boostByScoreFactor")
private val boostTotalCandidates = boostStats.stat("total_input_candidates")
private val softRankTotalCandidates = softUprankStats.stat("total_input_candidates")
private val softRankNumCandidatesCreators = softUprankStats.counter("candidates_from_creators")
private val softRankNumCandidatesNonCreators =
softUprankStats.counter("candidates_not_from_creators")
private val boostNumCandidatesCreators = boostStats.counter("candidates_from_creators")
private val boostNumCandidatesNonCreators =
boostStats.counter("candidates_not_from_creators")
def boostSubscriptionCreator(
inputCandidatesFut: Future[Seq[CandidateDetails[PushCandidate]]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
inputCandidatesFut.flatMap { inputCandidates =>
boostTotalCandidates.add(inputCandidates.size)
val tweetAuthorIds = inputCandidates.flatMap {
case CandidateDetails(candidate: TweetCandidate with TweetAuthor, s) =>
candidate.authorId
case _ => None
}.toSet
FutureOps
.mapCollect(superFollowEligibilityUserStore.multiGet(tweetAuthorIds))
.map { creatorAuthorMap =>
val (upRankedCandidates, otherCandidates) = inputCandidates.partition {
case CandidateDetails(candidate: TweetCandidate with TweetAuthor, s) =>
candidate.authorId match {
case Some(authorId) =>
creatorAuthorMap(authorId).getOrElse(false)
case _ => false
}
case _ => false
}
boostNumCandidatesCreators.incr(upRankedCandidates.size)
boostNumCandidatesNonCreators.incr(otherCandidates.size)
upRankedCandidates ++ otherCandidates
}
}
}
def boostByScoreFactor(
inputCandidatesFut: Future[Seq[CandidateDetails[PushCandidate]]],
factor: Double = 1.0,
): Future[Seq[CandidateDetails[PushCandidate]]] = {
inputCandidatesFut.flatMap { inputCandidates =>
softRankTotalCandidates.add(inputCandidates.size)
val tweetAuthorIds = inputCandidates.flatMap {
case CandidateDetails(candidate: TweetCandidate with TweetAuthor, s) =>
candidate.authorId
case _ => None
}.toSet
FutureOps
.mapCollect(superFollowEligibilityUserStore.multiGet(tweetAuthorIds))
.flatMap { creatorAuthorMap =>
val (upRankedCandidates, otherCandidates) = inputCandidates.partition {
case CandidateDetails(candidate: TweetCandidate with TweetAuthor, s) =>
candidate.authorId match {
case Some(authorId) =>
creatorAuthorMap(authorId).getOrElse(false)
case _ => false
}
case _ => false
}
softRankNumCandidatesCreators.incr(upRankedCandidates.size)
softRankNumCandidatesNonCreators.incr(otherCandidates.size)
ModelBasedRanker.rankBySpecifiedScore(
inputCandidates,
candidate => {
val isFromCreator = candidate match {
case candidate: TweetCandidate with TweetAuthor =>
candidate.authorId match {
case Some(authorId) =>
creatorAuthorMap(authorId).getOrElse(false)
case _ => false
}
case _ => false
}
candidate.mrWeightedOpenOrNtabClickRankingProbability.map {
case Some(score) =>
if (isFromCreator) Some(score * factor)
else Some(score)
case _ => None
}
}
)
}
}
}
}

View File

@ -1,259 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.CandidateResult
import com.twitter.frigate.common.base.CandidateSource
import com.twitter.frigate.common.base.FetchRankFlowWithHydratedCandidates
import com.twitter.frigate.common.base.Invalid
import com.twitter.frigate.common.base.OK
import com.twitter.frigate.common.base.Response
import com.twitter.frigate.common.base.Result
import com.twitter.frigate.common.base.Stats.track
import com.twitter.frigate.common.base.Stats.trackSeq
import com.twitter.frigate.common.logger.MRLogger
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.adaptor.LoggedOutPushCandidateSourceGenerator
import com.twitter.frigate.pushservice.predicate.LoggedOutPreRankingPredicates
import com.twitter.frigate.pushservice.predicate.LoggedOutTargetPredicates
import com.twitter.frigate.pushservice.rank.LoggedOutRanker
import com.twitter.frigate.pushservice.take.LoggedOutRefreshForPushNotifier
import com.twitter.frigate.pushservice.scriber.MrRequestScribeHandler
import com.twitter.frigate.pushservice.target.LoggedOutPushTargetUserBuilder
import com.twitter.frigate.pushservice.thriftscala.LoggedOutRequest
import com.twitter.frigate.pushservice.thriftscala.LoggedOutResponse
import com.twitter.frigate.pushservice.thriftscala.PushContext
import com.twitter.hermit.predicate.NamedPredicate
import com.twitter.hermit.predicate.Predicate
import com.twitter.hermit.predicate.SequentialPredicate
import com.twitter.util.Future
class LoggedOutRefreshForPushHandler(
val loPushTargetUserBuilder: LoggedOutPushTargetUserBuilder,
val loPushCandidateSourceGenerator: LoggedOutPushCandidateSourceGenerator,
candidateHydrator: PushCandidateHydrator,
val loRanker: LoggedOutRanker,
val loRfphNotifier: LoggedOutRefreshForPushNotifier,
loMrRequestScriberNode: String
)(
globalStats: StatsReceiver)
extends FetchRankFlowWithHydratedCandidates[Target, RawCandidate, PushCandidate] {
val log = MRLogger("LORefreshForPushHandler")
implicit val statsReceiver: StatsReceiver =
globalStats.scope("LORefreshForPushHandler")
private val loggedOutBuildStats = statsReceiver.scope("logged_out_build_target")
private val loggedOutProcessStats = statsReceiver.scope("logged_out_process")
private val loggedOutNotifyStats = statsReceiver.scope("logged_out_notify")
private val loCandidateHydrationStats: StatsReceiver =
statsReceiver.scope("logged_out_candidate_hydration")
val mrLORequestCandidateScribeStats =
statsReceiver.scope("mr_logged_out_request_scribe_candidates")
val mrRequestScribeHandler =
new MrRequestScribeHandler(loMrRequestScriberNode, statsReceiver.scope("lo_mr_request_scribe"))
val loMrRequestTargetScribeStats = statsReceiver.scope("lo_mr_request_scribe_target")
lazy val loCandSourceEligibleCounter: Counter =
loCandidateStats.counter("logged_out_cand_source_eligible")
lazy val loCandSourceNotEligibleCounter: Counter =
loCandidateStats.counter("logged_out_cand_source_not_eligible")
lazy val allCandidatesCounter: Counter = statsReceiver.counter("all_logged_out_candidates")
val allCandidatesFilteredPreRank = filterStats.counter("all_logged_out_candidates_filtered")
override def targetPredicates(target: Target): List[Predicate[Target]] = List(
LoggedOutTargetPredicates.targetFatiguePredicate(),
LoggedOutTargetPredicates.loggedOutRecsHoldbackPredicate()
)
override def isTargetValid(target: Target): Future[Result] = {
val resultFut =
if (target.skipFilters) {
Future.value(OK)
} else {
predicateSeq(target).track(Seq(target)).map { resultArr =>
trackTargetPredStats(resultArr(0))
}
}
track(targetStats)(resultFut)
}
override def rank(
target: Target,
candidateDetails: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
loRanker.rank(candidateDetails)
}
override def validCandidates(
target: Target,
candidates: Seq[PushCandidate]
): Future[Seq[Result]] = {
Future.value(candidates.map { c => OK })
}
override def desiredCandidateCount(target: Target): Int = 1
private val loggedOutPreRankingPredicates =
LoggedOutPreRankingPredicates(filterStats.scope("logged_out_predicates"))
private val loggedOutPreRankingPredicateChain =
new SequentialPredicate[PushCandidate](loggedOutPreRankingPredicates)
override def filter(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[
(Seq[CandidateDetails[PushCandidate]], Seq[CandidateResult[PushCandidate, Result]])
] = {
val predicateChain = loggedOutPreRankingPredicateChain
predicateChain
.track(candidates.map(_.candidate))
.map { results =>
val resultForPreRankingFiltering =
results
.zip(candidates)
.foldLeft(
(
Seq.empty[CandidateDetails[PushCandidate]],
Seq.empty[CandidateResult[PushCandidate, Result]]
)
) {
case ((goodCandidates, filteredCandidates), (result, candidateDetails)) =>
result match {
case None =>
(goodCandidates :+ candidateDetails, filteredCandidates)
case Some(pred: NamedPredicate[_]) =>
val r = Invalid(Some(pred.name))
(
goodCandidates,
filteredCandidates :+ CandidateResult[PushCandidate, Result](
candidateDetails.candidate,
candidateDetails.source,
r
)
)
case Some(_) =>
val r = Invalid(Some("Filtered by un-named predicate"))
(
goodCandidates,
filteredCandidates :+ CandidateResult[PushCandidate, Result](
candidateDetails.candidate,
candidateDetails.source,
r
)
)
}
}
resultForPreRankingFiltering match {
case (validCandidates, _) if validCandidates.isEmpty && candidates.nonEmpty =>
allCandidatesFilteredPreRank.incr()
case _ => ()
}
resultForPreRankingFiltering
}
}
override def candidateSources(
target: Target
): Future[Seq[CandidateSource[Target, RawCandidate]]] = {
Future
.collect(loPushCandidateSourceGenerator.sources.map { cs =>
cs.isCandidateSourceAvailable(target).map { isEligible =>
if (isEligible) {
loCandSourceEligibleCounter.incr()
Some(cs)
} else {
loCandSourceNotEligibleCounter.incr()
None
}
}
}).map(_.flatten)
}
override def process(
target: Target,
externalCandidates: Seq[RawCandidate] = Nil
): Future[Response[PushCandidate, Result]] = {
isTargetValid(target).flatMap {
case OK =>
for {
candidatesFromSources <- trackSeq(fetchStats)(fetchCandidates(target))
externalCandidateDetails = externalCandidates.map(
CandidateDetails(_, "logged_out_refresh_for_push_handler_external_candidates"))
allCandidates = candidatesFromSources ++ externalCandidateDetails
hydratedCandidatesWithCopy <-
trackSeq(loCandidateHydrationStats)(hydrateCandidates(allCandidates))
(candidates, preRankingFilteredCandidates) <-
track(filterStats)(filter(target, hydratedCandidatesWithCopy))
rankedCandidates <- trackSeq(rankingStats)(rank(target, candidates))
allTakeCandidateResults <- track(takeStats)(
take(target, rankedCandidates, desiredCandidateCount(target))
)
_ <- track(mrLORequestCandidateScribeStats)(
mrRequestScribeHandler.scribeForCandidateFiltering(
target,
hydratedCandidatesWithCopy,
preRankingFilteredCandidates,
rankedCandidates,
rankedCandidates,
rankedCandidates,
allTakeCandidateResults
))
} yield {
val takeCandidateResults = allTakeCandidateResults.filterNot { candResult =>
candResult.result == MoreThanDesiredCandidates
}
val allCandidateResults = takeCandidateResults ++ preRankingFilteredCandidates
allCandidatesCounter.incr(allCandidateResults.size)
Response(OK, allCandidateResults)
}
case result: Result =>
for (_ <- track(loMrRequestTargetScribeStats)(
mrRequestScribeHandler.scribeForTargetFiltering(target, result))) yield {
Response(result, Nil)
}
}
}
def buildTarget(
guestId: Long,
inputPushContext: Option[PushContext]
): Future[Target] =
loPushTargetUserBuilder.buildTarget(guestId, inputPushContext)
/**
* Hydrate candidate by querying downstream services
*
* @param candidates - candidates
*
* @return - hydrated candidates
*/
override def hydrateCandidates(
candidates: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = candidateHydrator(candidates)
override def batchForCandidatesCheck(target: Target): Int = 1
def refreshAndSend(request: LoggedOutRequest): Future[LoggedOutResponse] = {
for {
target <- track(loggedOutBuildStats)(
loPushTargetUserBuilder.buildTarget(request.guestId, request.context))
response <- track(loggedOutProcessStats)(process(target, externalCandidates = Seq.empty))
loggedOutRefreshResponse <-
track(loggedOutNotifyStats)(loRfphNotifier.checkResponseAndNotify(response))
} yield {
loggedOutRefreshResponse
}
}
}

View File

@ -1,239 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.channels.common.thriftscala.ApiList
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base._
import com.twitter.frigate.common.rec_types.RecTypes.isInNetworkTweetType
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.TrendTweetPushCandidate
import com.twitter.frigate.pushservice.ml.PushMLModelScorer
import com.twitter.frigate.pushservice.model.candidate.CopyIds
import com.twitter.frigate.pushservice.refresh_handler.cross.CandidateCopyExpansion
import com.twitter.frigate.pushservice.util.CandidateHydrationUtil._
import com.twitter.frigate.pushservice.util.MrUserStateUtil
import com.twitter.frigate.pushservice.util.RelationshipUtil
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.hermit.predicate.socialgraph.RelationEdge
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
case class PushCandidateHydrator(
socialGraphServiceProcessStore: ReadableStore[RelationEdge, Boolean],
safeUserStore: ReadableStore[Long, User],
apiListStore: ReadableStore[Long, ApiList],
candidateCopyCross: CandidateCopyExpansion
)(
implicit statsReceiver: StatsReceiver,
implicit val weightedOpenOrNtabClickModelScorer: PushMLModelScorer) {
lazy val candidateWithCopyNumStat = statsReceiver.stat("candidate_with_copy_num")
lazy val hydratedCandidateStat = statsReceiver.scope("hydrated_candidates")
lazy val mrUserStateStat = statsReceiver.scope("mr_user_state")
lazy val queryStep = statsReceiver.scope("query_step")
lazy val relationEdgeWithoutDuplicateInQueryStep =
queryStep.counter("number_of_relationEdge_without_duplicate_in_query_step")
lazy val relationEdgeWithoutDuplicateInQueryStepDistribution =
queryStep.stat("number_of_relationEdge_without_duplicate_in_query_step_distribution")
case class Entities(
users: Set[Long] = Set.empty[Long],
relationshipEdges: Set[RelationEdge] = Set.empty[RelationEdge]) {
def merge(otherEntities: Entities): Entities = {
this.copy(
users = this.users ++ otherEntities.users,
relationshipEdges =
this.relationshipEdges ++ otherEntities.relationshipEdges
)
}
}
case class EntitiesMap(
userMap: Map[Long, User] = Map.empty[Long, User],
relationshipMap: Map[RelationEdge, Boolean] = Map.empty[RelationEdge, Boolean])
private def updateCandidateAndCrtStats(
candidate: RawCandidate,
candidateType: String,
numEntities: Int = 1
): Unit = {
statsReceiver
.scope(candidateType).scope(candidate.commonRecType.name).stat(
"totalEntitiesPerCandidateTypePerCrt").add(numEntities)
statsReceiver.scope(candidateType).stat("totalEntitiesPerCandidateType").add(numEntities)
}
private def collectEntities(
candidateDetailsSeq: Seq[CandidateDetails[RawCandidate]]
): Entities = {
candidateDetailsSeq
.map { candidateDetails =>
val pushCandidate = candidateDetails.candidate
val userEntities = pushCandidate match {
case tweetWithSocialContext: RawCandidate with TweetWithSocialContextTraits =>
val authorIdOpt = getAuthorIdFromTweetCandidate(tweetWithSocialContext)
val scUserIds = tweetWithSocialContext.socialContextUserIds.toSet
updateCandidateAndCrtStats(pushCandidate, "tweetWithSocialContext", scUserIds.size + 1)
Entities(users = scUserIds ++ authorIdOpt.toSet)
case _ => Entities()
}
val relationEntities = {
if (isInNetworkTweetType(pushCandidate.commonRecType)) {
Entities(
relationshipEdges =
RelationshipUtil.getPreCandidateRelationshipsForInNetworkTweets(pushCandidate).toSet
)
} else Entities()
}
userEntities.merge(relationEntities)
}
.foldLeft(Entities()) { (e1, e2) => e1.merge(e2) }
}
/**
* This method calls Gizmoduck and Social Graph Service, keep the results in EntitiesMap
* and passed onto the update candidate phase in the hydration step
*
* @param entities contains all userIds and relationEdges for all candidates
* @return EntitiesMap contains userMap and relationshipMap
*/
private def queryEntities(entities: Entities): Future[EntitiesMap] = {
relationEdgeWithoutDuplicateInQueryStep.incr(entities.relationshipEdges.size)
relationEdgeWithoutDuplicateInQueryStepDistribution.add(entities.relationshipEdges.size)
val relationshipMapFuture = Future
.collect(socialGraphServiceProcessStore.multiGet(entities.relationshipEdges))
.map { resultMap =>
resultMap.collect {
case (relationshipEdge, Some(res)) => relationshipEdge -> res
case (relationshipEdge, None) => relationshipEdge -> false
}
}
val userMapFuture = Future
.collect(safeUserStore.multiGet(entities.users))
.map { userMap =>
userMap.collect {
case (userId, Some(user)) =>
userId -> user
}
}
Future.join(userMapFuture, relationshipMapFuture).map {
case (uMap, rMap) => EntitiesMap(userMap = uMap, relationshipMap = rMap)
}
}
/**
* @param candidateDetails: recommendation candidates for a user
* @return sequence of candidates tagged with push and ntab copy id
*/
private def expandCandidatesWithCopy(
candidateDetails: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[(CandidateDetails[RawCandidate], CopyIds)]] = {
candidateCopyCross.expandCandidatesWithCopyId(candidateDetails)
}
def updateCandidates(
candidateDetailsWithCopies: Seq[(CandidateDetails[RawCandidate], CopyIds)],
entitiesMaps: EntitiesMap
): Seq[CandidateDetails[PushCandidate]] = {
candidateDetailsWithCopies.map {
case (candidateDetail, copyIds) =>
val pushCandidate = candidateDetail.candidate
val userMap = entitiesMaps.userMap
val relationshipMap = entitiesMaps.relationshipMap
val hydratedCandidate = pushCandidate match {
case f1TweetCandidate: F1FirstDegree =>
getHydratedCandidateForF1FirstDegreeTweet(
f1TweetCandidate,
userMap,
relationshipMap,
copyIds)
case tweetRetweet: TweetRetweetCandidate =>
getHydratedCandidateForTweetRetweet(tweetRetweet, userMap, copyIds)
case tweetFavorite: TweetFavoriteCandidate =>
getHydratedCandidateForTweetFavorite(tweetFavorite, userMap, copyIds)
case tripTweetCandidate: OutOfNetworkTweetCandidate with TripCandidate =>
getHydratedCandidateForTripTweetCandidate(tripTweetCandidate, userMap, copyIds)
case outOfNetworkTweetCandidate: OutOfNetworkTweetCandidate with TopicCandidate =>
getHydratedCandidateForOutOfNetworkTweetCandidate(
outOfNetworkTweetCandidate,
userMap,
copyIds)
case topicProofTweetCandidate: TopicProofTweetCandidate =>
getHydratedTopicProofTweetCandidate(topicProofTweetCandidate, userMap, copyIds)
case subscribedSearchTweetCandidate: SubscribedSearchTweetCandidate =>
getHydratedSubscribedSearchTweetCandidate(
subscribedSearchTweetCandidate,
userMap,
copyIds)
case listRecommendation: ListPushCandidate =>
getHydratedListCandidate(apiListStore, listRecommendation, copyIds)
case discoverTwitterCandidate: DiscoverTwitterCandidate =>
getHydratedCandidateForDiscoverTwitterCandidate(discoverTwitterCandidate, copyIds)
case topTweetImpressionsCandidate: TopTweetImpressionsCandidate =>
getHydratedCandidateForTopTweetImpressionsCandidate(
topTweetImpressionsCandidate,
copyIds)
case trendTweetCandidate: TrendTweetCandidate =>
new TrendTweetPushCandidate(
trendTweetCandidate,
trendTweetCandidate.authorId.flatMap(userMap.get),
copyIds)
case unknownCandidate =>
throw new IllegalArgumentException(
s"Incorrect candidate for hydration: ${unknownCandidate.commonRecType}")
}
CandidateDetails(
hydratedCandidate,
source = candidateDetail.source
)
}
}
def apply(
candidateDetails: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val isLoggedOutRequest =
candidateDetails.headOption.exists(_.candidate.target.isLoggedOutUser)
if (!isLoggedOutRequest) {
candidateDetails.headOption.map { cd =>
MrUserStateUtil.updateMrUserStateStats(cd.candidate.target)(mrUserStateStat)
}
}
expandCandidatesWithCopy(candidateDetails).flatMap { candidateDetailsWithCopy =>
candidateWithCopyNumStat.add(candidateDetailsWithCopy.size)
val entities = collectEntities(candidateDetailsWithCopy.map(_._1))
queryEntities(entities).flatMap { entitiesMap =>
val updatedCandidates = updateCandidates(candidateDetailsWithCopy, entitiesMap)
updatedCandidates.foreach { cand =>
hydratedCandidateStat.counter(cand.candidate.commonRecType.name).incr()
}
Future.value(updatedCandidates)
}
}
}
}

View File

@ -1,69 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.FeatureMap
import com.twitter.frigate.data_pipeline.features_common.MrRequestContextForFeatureStore
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.ml.HydrationContextBuilder
import com.twitter.frigate.pushservice.params.PushParams
import com.twitter.frigate.pushservice.util.MrUserStateUtil
import com.twitter.nrel.heavyranker.FeatureHydrator
import com.twitter.util.Future
class RFPHFeatureHydrator(
featureHydrator: FeatureHydrator
)(
implicit globalStats: StatsReceiver) {
implicit val statsReceiver: StatsReceiver =
globalStats.scope("RefreshForPushHandler")
//stat for feature hydration
private val featureHydrationEnabledCounter = statsReceiver.counter("featureHydrationEnabled")
private val mrUserStateStat = statsReceiver.scope("mr_user_state")
private def hydrateFromRelevanceHydrator(
candidateDetails: Seq[CandidateDetails[PushCandidate]],
mrRequestContextForFeatureStore: MrRequestContextForFeatureStore
): Future[Unit] = {
val pushCandidates = candidateDetails.map(_.candidate)
val candidatesAndContextsFut = Future.collect(pushCandidates.map { pc =>
val contextFut = HydrationContextBuilder.build(pc)
contextFut.map { ctx => (pc, ctx) }
})
candidatesAndContextsFut.flatMap { candidatesAndContexts =>
val contexts = candidatesAndContexts.map(_._2)
val resultsFut = featureHydrator.hydrateCandidate(contexts, mrRequestContextForFeatureStore)
resultsFut.map { hydrationResult =>
candidatesAndContexts.foreach {
case (pushCandidate, context) =>
val resultFeatures = hydrationResult.getOrElse(context, FeatureMap())
pushCandidate.mergeFeatures(resultFeatures)
}
}
}
}
def candidateFeatureHydration(
candidateDetails: Seq[CandidateDetails[PushCandidate]],
mrRequestContextForFeatureStore: MrRequestContextForFeatureStore
): Future[Seq[CandidateDetails[PushCandidate]]] = {
candidateDetails.headOption match {
case Some(cand) =>
val target = cand.candidate.target
MrUserStateUtil.updateMrUserStateStats(target)(mrUserStateStat)
if (target.params(PushParams.DisableAllRelevanceParam)) {
Future.value(candidateDetails)
} else {
featureHydrationEnabledCounter.incr()
for {
_ <- hydrateFromRelevanceHydrator(candidateDetails, mrRequestContextForFeatureStore)
} yield {
candidateDetails
}
}
case _ => Future.Nil
}
}
}

View File

@ -1,104 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base._
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.predicate.PreRankingPredicates
import com.twitter.hermit.predicate.NamedPredicate
import com.twitter.hermit.predicate.SequentialPredicate
import com.twitter.util._
class RFPHPrerankFilter(
)(
globalStats: StatsReceiver) {
def filter(
target: Target,
hydratedCandidates: Seq[CandidateDetails[PushCandidate]]
): Future[
(Seq[CandidateDetails[PushCandidate]], Seq[CandidateResult[PushCandidate, Result]])
] = {
lazy val filterStats: StatsReceiver = globalStats.scope("RefreshForPushHandler/filter")
lazy val okFilterCounter: Counter = filterStats.counter("ok")
lazy val invalidFilterCounter: Counter = filterStats.counter("invalid")
lazy val invalidFilterStat: StatsReceiver = filterStats.scope("invalid")
lazy val invalidFilterReasonStat: StatsReceiver = invalidFilterStat.scope("reason")
val allCandidatesFilteredPreRank = filterStats.counter("all_candidates_filtered")
lazy val preRankingPredicates = PreRankingPredicates(
filterStats.scope("predicates")
)
lazy val preRankingPredicateChain =
new SequentialPredicate[PushCandidate](preRankingPredicates)
val predicateChain = if (target.pushContext.exists(_.predicatesToEnable.exists(_.nonEmpty))) {
val predicatesToEnable = target.pushContext.flatMap(_.predicatesToEnable).getOrElse(Nil)
new SequentialPredicate[PushCandidate](preRankingPredicates.filter { pred =>
predicatesToEnable.contains(pred.name)
})
} else preRankingPredicateChain
predicateChain
.track(hydratedCandidates.map(_.candidate))
.map { results =>
val resultForPreRankFiltering = results
.zip(hydratedCandidates)
.foldLeft(
(
Seq.empty[CandidateDetails[PushCandidate]],
Seq.empty[CandidateResult[PushCandidate, Result]]
)
) {
case ((goodCandidates, filteredCandidates), (result, candidateDetails)) =>
result match {
case None =>
okFilterCounter.incr()
(goodCandidates :+ candidateDetails, filteredCandidates)
case Some(pred: NamedPredicate[_]) =>
invalidFilterCounter.incr()
invalidFilterReasonStat.counter(pred.name).incr()
invalidFilterReasonStat
.scope(candidateDetails.candidate.commonRecType.toString).counter(
pred.name).incr()
val r = Invalid(Some(pred.name))
(
goodCandidates,
filteredCandidates :+ CandidateResult[PushCandidate, Result](
candidateDetails.candidate,
candidateDetails.source,
r
)
)
case Some(_) =>
invalidFilterCounter.incr()
invalidFilterReasonStat.counter("unknown").incr()
invalidFilterReasonStat
.scope(candidateDetails.candidate.commonRecType.toString).counter(
"unknown").incr()
val r = Invalid(Some("Filtered by un-named predicate"))
(
goodCandidates,
filteredCandidates :+ CandidateResult[PushCandidate, Result](
candidateDetails.candidate,
candidateDetails.source,
r
)
)
}
}
resultForPreRankFiltering match {
case (validCandidates, _) if validCandidates.isEmpty && hydratedCandidates.nonEmpty =>
allCandidatesFilteredPreRank.incr()
case _ => ()
}
resultForPreRankFiltering
}
}
}

View File

@ -1,34 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.TargetUser
import com.twitter.frigate.common.candidate.TargetABDecider
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.frigate.pushservice.target.TargetScoringDetails
class RFPHRestrictStep()(implicit stats: StatsReceiver) {
private val statsReceiver: StatsReceiver = stats.scope("RefreshForPushHandler")
private val restrictStepStats: StatsReceiver = statsReceiver.scope("restrict")
private val restrictStepNumCandidatesDroppedStat: Stat =
restrictStepStats.stat("candidates_dropped")
/**
* Limit the number of candidates that enter the Take step
*/
def restrict(
target: TargetUser with TargetABDecider with TargetScoringDetails,
candidates: Seq[CandidateDetails[PushCandidate]]
): (Seq[CandidateDetails[PushCandidate]], Seq[CandidateDetails[PushCandidate]]) = {
if (target.params(PushFeatureSwitchParams.EnableRestrictStep)) {
val restrictSizeParam = PushFeatureSwitchParams.RestrictStepSize
val (newCandidates, filteredCandidates) = candidates.splitAt(target.params(restrictSizeParam))
val numDropped = candidates.length - newCandidates.length
restrictStepNumCandidatesDroppedStat.add(numDropped)
(newCandidates, filteredCandidates)
} else (candidates, Seq.empty)
}
}

View File

@ -1,77 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.thriftscala.CommonRecommendationType
class RFPHStatsRecorder(implicit statsReceiver: StatsReceiver) {
private val selectedCandidateScoreStats: StatsReceiver =
statsReceiver.scope("score_of_sent_candidate_times_10000")
private val emptyScoreStats: StatsReceiver =
statsReceiver.scope("score_of_sent_candidate_empty")
def trackPredictionScoreStats(candidate: PushCandidate): Unit = {
candidate.mrWeightedOpenOrNtabClickRankingProbability.foreach {
case Some(s) =>
selectedCandidateScoreStats
.stat("weighted_open_or_ntab_click_ranking")
.add((s * 10000).toFloat)
case None =>
emptyScoreStats.counter("weighted_open_or_ntab_click_ranking").incr()
}
candidate.mrWeightedOpenOrNtabClickFilteringProbability.foreach {
case Some(s) =>
selectedCandidateScoreStats
.stat("weighted_open_or_ntab_click_filtering")
.add((s * 10000).toFloat)
case None =>
emptyScoreStats.counter("weighted_open_or_ntab_click_filtering").incr()
}
candidate.mrWeightedOpenOrNtabClickRankingProbability.foreach {
case Some(s) =>
selectedCandidateScoreStats
.scope(candidate.commonRecType.toString)
.stat("weighted_open_or_ntab_click_ranking")
.add((s * 10000).toFloat)
case None =>
emptyScoreStats
.scope(candidate.commonRecType.toString)
.counter("weighted_open_or_ntab_click_ranking")
.incr()
}
}
def refreshRequestExceptionStats(
exception: Throwable,
bStats: StatsReceiver
): Unit = {
bStats.counter("failures").incr()
bStats.scope("failures").counter(exception.getClass.getCanonicalName).incr()
}
def loggedOutRequestExceptionStats(
exception: Throwable,
bStats: StatsReceiver
): Unit = {
bStats.counter("logged_out_failures").incr()
bStats.scope("failures").counter(exception.getClass.getCanonicalName).incr()
}
def rankDistributionStats(
candidatesDetails: Seq[CandidateDetails[PushCandidate]],
numRecsPerTypeStat: (CommonRecommendationType => Stat)
): Unit = {
candidatesDetails
.groupBy { c =>
c.candidate.commonRecType
}
.mapValues { s =>
s.size
}
.foreach { case (crt, numRecs) => numRecsPerTypeStat(crt).add(numRecs) }
}
}

View File

@ -1,292 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.Counter
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.Stats.track
import com.twitter.frigate.common.base.Stats.trackSeq
import com.twitter.frigate.common.base._
import com.twitter.frigate.common.logger.MRLogger
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.adaptor._
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.frigate.pushservice.rank.RFPHLightRanker
import com.twitter.frigate.pushservice.rank.RFPHRanker
import com.twitter.frigate.pushservice.scriber.MrRequestScribeHandler
import com.twitter.frigate.pushservice.take.candidate_validator.RFPHCandidateValidator
import com.twitter.frigate.pushservice.target.PushTargetUserBuilder
import com.twitter.frigate.pushservice.target.RFPHTargetPredicates
import com.twitter.frigate.pushservice.util.RFPHTakeStepUtil
import com.twitter.frigate.pushservice.util.AdhocStatsUtil
import com.twitter.frigate.pushservice.thriftscala.PushContext
import com.twitter.frigate.pushservice.thriftscala.RefreshRequest
import com.twitter.frigate.pushservice.thriftscala.RefreshResponse
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.hermit.predicate.Predicate
import com.twitter.timelines.configapi.FeatureValue
import com.twitter.util._
case class ResultWithDebugInfo(result: Result, predicateResults: Seq[PredicateWithResult])
class RefreshForPushHandler(
val pushTargetUserBuilder: PushTargetUserBuilder,
val candSourceGenerator: PushCandidateSourceGenerator,
rfphRanker: RFPHRanker,
candidateHydrator: PushCandidateHydrator,
candidateValidator: RFPHCandidateValidator,
rfphTakeStepUtil: RFPHTakeStepUtil,
rfphRestrictStep: RFPHRestrictStep,
val rfphNotifier: RefreshForPushNotifier,
rfphStatsRecorder: RFPHStatsRecorder,
mrRequestScriberNode: String,
rfphFeatureHydrator: RFPHFeatureHydrator,
rfphPrerankFilter: RFPHPrerankFilter,
rfphLightRanker: RFPHLightRanker
)(
globalStats: StatsReceiver)
extends FetchRankFlowWithHydratedCandidates[Target, RawCandidate, PushCandidate] {
val log = MRLogger("RefreshForPushHandler")
implicit val statsReceiver: StatsReceiver =
globalStats.scope("RefreshForPushHandler")
private val maxCandidatesToBatchInTakeStat: Stat =
statsReceiver.stat("max_cands_to_batch_in_take")
private val rfphRequestCounter = statsReceiver.counter("requests")
private val buildTargetStats = statsReceiver.scope("build_target")
private val processStats = statsReceiver.scope("process")
private val notifyStats = statsReceiver.scope("notify")
private val lightRankingStats: StatsReceiver = statsReceiver.scope("light_ranking")
private val reRankingStats: StatsReceiver = statsReceiver.scope("rerank")
private val featureHydrationLatency: StatsReceiver =
statsReceiver.scope("featureHydrationLatency")
private val candidateHydrationStats: StatsReceiver = statsReceiver.scope("candidate_hydration")
lazy val candSourceEligibleCounter: Counter =
candidateStats.counter("cand_source_eligible")
lazy val candSourceNotEligibleCounter: Counter =
candidateStats.counter("cand_source_not_eligible")
//pre-ranking stats
val allCandidatesFilteredPreRank = filterStats.counter("all_candidates_filtered")
// total invalid candidates
val totalStats: StatsReceiver = statsReceiver.scope("total")
val totalInvalidCandidatesStat: Stat = totalStats.stat("candidates_invalid")
val mrRequestScribeBuiltStats: Counter = statsReceiver.counter("mr_request_scribe_built")
val mrRequestCandidateScribeStats = statsReceiver.scope("mr_request_scribe_candidates")
val mrRequestTargetScribeStats = statsReceiver.scope("mr_request_scribe_target")
val mrRequestScribeHandler =
new MrRequestScribeHandler(mrRequestScriberNode, statsReceiver.scope("mr_request_scribe"))
val adhocStatsUtil = new AdhocStatsUtil(statsReceiver.scope("adhoc_stats"))
private def numRecsPerTypeStat(crt: CommonRecommendationType) =
fetchStats.scope(crt.toString).stat("dist")
// static list of target predicates
private val targetPredicates = RFPHTargetPredicates(targetStats.scope("predicates"))
def buildTarget(
userId: Long,
inputPushContext: Option[PushContext],
forcedFeatureValues: Option[Map[String, FeatureValue]] = None
): Future[Target] =
pushTargetUserBuilder.buildTarget(userId, inputPushContext, forcedFeatureValues)
override def targetPredicates(target: Target): List[Predicate[Target]] = targetPredicates
override def isTargetValid(target: Target): Future[Result] = {
val resultFut = if (target.skipFilters) {
Future.value(trackTargetPredStats(None))
} else {
predicateSeq(target).track(Seq(target)).map { resultArr =>
trackTargetPredStats(resultArr(0))
}
}
track(targetStats)(resultFut)
}
override def candidateSources(
target: Target
): Future[Seq[CandidateSource[Target, RawCandidate]]] = {
Future
.collect(candSourceGenerator.sources.map { cs =>
cs.isCandidateSourceAvailable(target).map { isEligible =>
if (isEligible) {
candSourceEligibleCounter.incr()
Some(cs)
} else {
candSourceNotEligibleCounter.incr()
None
}
}
}).map(_.flatten)
}
override def updateCandidateCounter(
candidateResults: Seq[CandidateResult[PushCandidate, Result]]
): Unit = {
candidateResults.foreach {
case candidateResult if candidateResult.result == OK =>
okCandidateCounter.incr()
case candidateResult if candidateResult.result.isInstanceOf[Invalid] =>
invalidCandidateCounter.incr()
case _ =>
}
}
override def hydrateCandidates(
candidates: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = candidateHydrator(candidates)
override def filter(
target: Target,
hydratedCandidates: Seq[CandidateDetails[PushCandidate]]
): Future[
(Seq[CandidateDetails[PushCandidate]], Seq[CandidateResult[PushCandidate, Result]])
] = rfphPrerankFilter.filter(target, hydratedCandidates)
def lightRankAndTake(
target: Target,
candidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
rfphLightRanker.rank(target, candidates)
}
override def rank(
target: Target,
candidatesDetails: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
val featureHydratedCandidatesFut = trackSeq(featureHydrationLatency)(
rfphFeatureHydrator
.candidateFeatureHydration(candidatesDetails, target.mrRequestContextForFeatureStore)
)
featureHydratedCandidatesFut.flatMap { featureHydratedCandidates =>
rfphStatsRecorder.rankDistributionStats(featureHydratedCandidates, numRecsPerTypeStat)
rfphRanker.initialRank(target, candidatesDetails)
}
}
def reRank(
target: Target,
rankedCandidates: Seq[CandidateDetails[PushCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
rfphRanker.reRank(target, rankedCandidates)
}
override def validCandidates(
target: Target,
candidates: Seq[PushCandidate]
): Future[Seq[Result]] = {
Future.collect(candidates.map { candidate =>
rfphTakeStepUtil.isCandidateValid(candidate, candidateValidator).map(res => res.result)
})
}
override def desiredCandidateCount(target: Target): Int = target.desiredCandidateCount
override def batchForCandidatesCheck(target: Target): Int = {
val fsParam = PushFeatureSwitchParams.NumberOfMaxCandidatesToBatchInRFPHTakeStep
val maxToBatch = target.params(fsParam)
maxCandidatesToBatchInTakeStat.add(maxToBatch)
maxToBatch
}
override def process(
target: Target,
externalCandidates: Seq[RawCandidate] = Nil
): Future[Response[PushCandidate, Result]] = {
isTargetValid(target).flatMap {
case OK =>
for {
candidatesFromSources <- trackSeq(fetchStats)(fetchCandidates(target))
externalCandidateDetails = externalCandidates.map(
CandidateDetails(_, "refresh_for_push_handler_external_candidate"))
allCandidates = candidatesFromSources ++ externalCandidateDetails
hydratedCandidatesWithCopy <-
trackSeq(candidateHydrationStats)(hydrateCandidates(allCandidates))
_ = adhocStatsUtil.getCandidateSourceStats(hydratedCandidatesWithCopy)
(candidates, preRankingFilteredCandidates) <-
track(filterStats)(filter(target, hydratedCandidatesWithCopy))
_ = adhocStatsUtil.getPreRankingFilterStats(preRankingFilteredCandidates)
lightRankerFilteredCandidates <-
trackSeq(lightRankingStats)(lightRankAndTake(target, candidates))
_ = adhocStatsUtil.getLightRankingStats(lightRankerFilteredCandidates)
rankedCandidates <- trackSeq(rankingStats)(rank(target, lightRankerFilteredCandidates))
_ = adhocStatsUtil.getRankingStats(rankedCandidates)
rerankedCandidates <- trackSeq(reRankingStats)(reRank(target, rankedCandidates))
_ = adhocStatsUtil.getReRankingStats(rerankedCandidates)
(restrictedCandidates, restrictFilteredCandidates) =
rfphRestrictStep.restrict(target, rerankedCandidates)
allTakeCandidateResults <- track(takeStats)(
take(target, restrictedCandidates, desiredCandidateCount(target))
)
_ = adhocStatsUtil.getTakeCandidateResultStats(allTakeCandidateResults)
_ <- track(mrRequestCandidateScribeStats)(
mrRequestScribeHandler.scribeForCandidateFiltering(
target,
hydratedCandidatesWithCopy,
preRankingFilteredCandidates,
rankedCandidates,
rerankedCandidates,
restrictFilteredCandidates,
allTakeCandidateResults
))
} yield {
/**
* Take processes post restrict step candidates and returns both:
* 1. valid + invalid candidates
* 2. Candidates that are not processed (more than desired) + restricted candidates
* We need #2 only for importance sampling
*/
val takeCandidateResults =
allTakeCandidateResults.filterNot { candResult =>
candResult.result == MoreThanDesiredCandidates
}
val totalInvalidCandidates = {
preRankingFilteredCandidates.size + //pre-ranking filtered candidates
(rerankedCandidates.length - restrictedCandidates.length) + //candidates reject in restrict step
takeCandidateResults.count(_.result != OK) //candidates reject in take step
}
takeInvalidCandidateDist.add(
takeCandidateResults
.count(_.result != OK)
) // take step invalid candidates
totalInvalidCandidatesStat.add(totalInvalidCandidates)
val allCandidateResults = takeCandidateResults ++ preRankingFilteredCandidates
Response(OK, allCandidateResults)
}
case result: Result =>
for (_ <- track(mrRequestTargetScribeStats)(
mrRequestScribeHandler.scribeForTargetFiltering(target, result))) yield {
mrRequestScribeBuiltStats.incr()
Response(result, Nil)
}
}
}
def refreshAndSend(request: RefreshRequest): Future[RefreshResponse] = {
rfphRequestCounter.incr()
for {
target <- track(buildTargetStats)(
pushTargetUserBuilder
.buildTarget(request.userId, request.context))
response <- track(processStats)(process(target, externalCandidates = Seq.empty))
refreshResponse <- track(notifyStats)(rfphNotifier.checkResponseAndNotify(response, target))
} yield {
refreshResponse
}
}
}

View File

@ -1,128 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler
import com.twitter.finagle.stats.BroadcastStatsReceiver
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.Stats.track
import com.twitter.frigate.common.base._
import com.twitter.frigate.common.config.CommonConstants
import com.twitter.frigate.common.util.PushServiceUtil.FilteredRefreshResponseFut
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.take.CandidateNotifier
import com.twitter.frigate.pushservice.util.ResponseStatsTrackUtils.trackStatsForResponseToRequest
import com.twitter.frigate.pushservice.thriftscala.PushStatus
import com.twitter.frigate.pushservice.thriftscala.RefreshResponse
import com.twitter.util.Future
import com.twitter.util.JavaTimer
import com.twitter.util.Timer
class RefreshForPushNotifier(
rfphStatsRecorder: RFPHStatsRecorder,
candidateNotifier: CandidateNotifier
)(
globalStats: StatsReceiver) {
private implicit val statsReceiver: StatsReceiver =
globalStats.scope("RefreshForPushHandler")
private val pushStats: StatsReceiver = statsReceiver.scope("push")
private val sendLatency: StatsReceiver = statsReceiver.scope("send_handler")
implicit private val timer: Timer = new JavaTimer(true)
private def notify(
candidatesResult: CandidateResult[PushCandidate, Result],
target: Target,
receivers: Seq[StatsReceiver]
): Future[RefreshResponse] = {
val candidate = candidatesResult.candidate
val predsResult = candidatesResult.result
if (predsResult != OK) {
val invalidResult = predsResult
invalidResult match {
case Invalid(Some(reason)) =>
Future.value(RefreshResponse(PushStatus.Filtered, Some(reason)))
case _ =>
Future.value(RefreshResponse(PushStatus.Filtered, None))
}
} else {
rfphStatsRecorder.trackPredictionScoreStats(candidate)
val isQualityUprankingCandidate = candidate.mrQualityUprankingBoost.isDefined
val commonRecTypeStats = Seq(
statsReceiver.scope(candidate.commonRecType.toString),
globalStats.scope(candidate.commonRecType.toString)
)
val qualityUprankingStats = Seq(
statsReceiver.scope("QualityUprankingCandidates").scope(candidate.commonRecType.toString),
globalStats.scope("QualityUprankingCandidates").scope(candidate.commonRecType.toString)
)
val receiversWithRecTypeStats = {
if (isQualityUprankingCandidate) {
receivers ++ commonRecTypeStats ++ qualityUprankingStats
} else {
receivers ++ commonRecTypeStats
}
}
track(sendLatency)(candidateNotifier.notify(candidate).map { res =>
trackStatsForResponseToRequest(
candidate.commonRecType,
candidate.target,
res,
receiversWithRecTypeStats
)(globalStats)
RefreshResponse(res.status)
})
}
}
def checkResponseAndNotify(
response: Response[PushCandidate, Result],
targetUserContext: Target
): Future[RefreshResponse] = {
val receivers = Seq(statsReceiver)
val refreshResponse = response match {
case Response(OK, processedCandidates) =>
// valid rec candidates
val validCandidates = processedCandidates.filter(_.result == OK)
// top rec candidate
validCandidates.headOption match {
case Some(candidatesResult) =>
candidatesResult.result match {
case OK =>
notify(candidatesResult, targetUserContext, receivers)
.onSuccess { nr =>
pushStats.scope("result").counter(nr.status.name).incr()
}
case _ =>
targetUserContext.isTeamMember.flatMap { isTeamMember =>
FilteredRefreshResponseFut
}
}
case _ =>
FilteredRefreshResponseFut
}
case Response(Invalid(reason), _) =>
// invalid target with known reason
FilteredRefreshResponseFut.map(_.copy(targetFilteredBy = reason))
case _ =>
// invalid target
FilteredRefreshResponseFut
}
val bStats = BroadcastStatsReceiver(receivers)
Stat
.timeFuture(bStats.stat("latency"))(
refreshResponse
.raiseWithin(CommonConstants.maxPushRequestDuration)
)
.onFailure { exception =>
rfphStatsRecorder.refreshRequestExceptionStats(exception, bStats)
}
}
}

View File

@ -1,79 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.util.MRNtabCopy
import com.twitter.frigate.common.util.MRPushCopy
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.util.Future
abstract class BaseCopyFramework(statsReceiver: StatsReceiver) {
private val NoAvailableCopyStat = statsReceiver.scope("no_copy_for_crt")
private val NoAvailableNtabCopyStat = statsReceiver.scope("no_ntab_copy")
/**
* Instantiate push copy filters
*/
protected final val copyFilters = new CopyFilters(statsReceiver.scope("filters"))
/**
*
* The following method fetches all the push copies for a [[com.twitter.frigate.thriftscala.CommonRecommendationType]]
* associated with a candidate and then filters the eligible copies based on
* [[PushTypes.PushCandidate]] features. These filters are defined in
* [[CopyFilters]]
*
* @param rawCandidate - [[RawCandidate]] object representing a recommendation candidate
*
* @return - set of eligible push copies for a given candidate
*/
protected[cross] final def getEligiblePushCopiesFromCandidate(
rawCandidate: RawCandidate
): Future[Seq[MRPushCopy]] = {
val pushCopiesFromRectype = CandidateToCopy.getPushCopiesFromRectype(rawCandidate.commonRecType)
if (pushCopiesFromRectype.isEmpty) {
NoAvailableCopyStat.counter(rawCandidate.commonRecType.name).incr()
throw new IllegalStateException(s"No Copy defined for CRT: " + rawCandidate.commonRecType)
}
pushCopiesFromRectype
.map(pushCopySet => copyFilters.execute(rawCandidate, pushCopySet.toSeq))
.getOrElse(Future.value(Seq.empty))
}
/**
*
* This method essentially forms the base for cross-step for the MagicRecs Copy Framework. Given
* a recommendation type this returns a set of tuples wherein each tuple is a pair of push and
* ntab copy eligible for the said recommendation type
*
* @param rawCandidate - [[RawCandidate]] object representing a recommendation candidate
* @return - Set of eligible [[MRPushCopy]], Option[[MRNtabCopy]] for a given recommendation type
*/
protected[cross] final def getEligiblePushAndNtabCopiesFromCandidate(
rawCandidate: RawCandidate
): Future[Seq[(MRPushCopy, Option[MRNtabCopy])]] = {
val eligiblePushCopies = getEligiblePushCopiesFromCandidate(rawCandidate)
eligiblePushCopies.map { pushCopies =>
val setBuilder = Set.newBuilder[(MRPushCopy, Option[MRNtabCopy])]
pushCopies.foreach { pushCopy =>
val ntabCopies = CandidateToCopy.getNtabcopiesFromPushcopy(pushCopy)
val pushNtabCopyPairs = ntabCopies match {
case Some(ntabCopySet) =>
if (ntabCopySet.isEmpty) {
NoAvailableNtabCopyStat.counter(s"copy_id: ${pushCopy.copyId}").incr()
Set(pushCopy -> None)
} // push copy only
else ntabCopySet.map(pushCopy -> Some(_))
case None =>
Set.empty[(MRPushCopy, Option[MRNtabCopy])] // no push or ntab copy
}
setBuilder ++= pushNtabCopyPairs
}
setBuilder.result().toSeq
}
}
}

View File

@ -1,56 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.util.MRNtabCopy
import com.twitter.frigate.common.util.MRPushCopy
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.candidate.CopyIds
import com.twitter.util.Future
/**
* @param statsReceiver - stats receiver object
*/
class CandidateCopyExpansion(statsReceiver: StatsReceiver)
extends BaseCopyFramework(statsReceiver) {
/**
*
* Given a [[CandidateDetails]] object representing a push recommendation candidate this method
* expands it to multiple candidates, each tagged with a push copy id and ntab copy id to
* represent the eligible copies for the given recommendation candidate
*
* @param candidateDetails - [[CandidateDetails]] objects containing a recommendation candidate
*
* @return - list of tuples of [[PushTypes.RawCandidate]] and [[CopyIds]]
*/
private final def crossCandidateDetailsWithCopyId(
candidateDetails: CandidateDetails[RawCandidate]
): Future[Seq[(CandidateDetails[RawCandidate], CopyIds)]] = {
val eligibleCopyPairs = getEligiblePushAndNtabCopiesFromCandidate(candidateDetails.candidate)
val copyPairs = eligibleCopyPairs.map(_.map {
case (pushCopy: MRPushCopy, ntabCopy: Option[MRNtabCopy]) =>
CopyIds(
pushCopyId = Some(pushCopy.copyId),
ntabCopyId = ntabCopy.map(_.copyId)
)
})
copyPairs.map(_.map((candidateDetails, _)))
}
/**
*
* This method takes as input a list of [[CandidateDetails]] objects which contain the push
* recommendation candidates for a given target user. It expands each input candidate into
* multiple candidates, each tagged with a push copy id and ntab copy id to represent the eligible
* copies for the given recommendation candidate
*
* @param candidateDetailsSeq - list of fetched candidates for push recommendation
* @return - list of tuples of [[RawCandidate]] and [[CopyIds]]
*/
final def expandCandidatesWithCopyId(
candidateDetailsSeq: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[(CandidateDetails[RawCandidate], CopyIds)]] =
Future.collect(candidateDetailsSeq.map(crossCandidateDetailsWithCopyId)).map(_.flatten)
}

View File

@ -1,11 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.frigate.common.util.MRPushCopy
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
/**
*
* @param candidate: [[RawCandidate]] is a recommendation candidate
* @param pushCopy: [[MRPushCopy]] eligible for candidate
*/
case class CandidateCopyPair(candidate: RawCandidate, pushCopy: MRPushCopy)

View File

@ -1,263 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.frigate.common.util.MrNtabCopyObjects
import com.twitter.frigate.common.util.MrPushCopyObjects
import com.twitter.frigate.common.util._
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.CommonRecommendationType._
object CandidateToCopy {
// Static map from a CommonRecommendationType to set of eligible push notification copies
private[cross] val rectypeToPushCopy: Map[CommonRecommendationType, Set[
MRPushCopy
]] =
Map[CommonRecommendationType, Set[MRPushCopy]](
F1FirstdegreeTweet -> Set(
MrPushCopyObjects.FirstDegreeJustTweetedBoldTitle
),
F1FirstdegreePhoto -> Set(
MrPushCopyObjects.FirstDegreePhotoJustTweetedBoldTitle
),
F1FirstdegreeVideo -> Set(
MrPushCopyObjects.FirstDegreeVideoJustTweetedBoldTitle
),
TweetRetweet -> Set(
MrPushCopyObjects.TweetRetweetWithOneDisplaySocialContextsWithText,
MrPushCopyObjects.TweetRetweetWithTwoDisplaySocialContextsWithText,
MrPushCopyObjects.TweetRetweetWithOneDisplayAndKOtherSocialContextsWithText
),
TweetRetweetPhoto -> Set(
MrPushCopyObjects.TweetRetweetPhotoWithOneDisplaySocialContextWithText,
MrPushCopyObjects.TweetRetweetPhotoWithTwoDisplaySocialContextsWithText,
MrPushCopyObjects.TweetRetweetPhotoWithOneDisplayAndKOtherSocialContextsWithText
),
TweetRetweetVideo -> Set(
MrPushCopyObjects.TweetRetweetVideoWithOneDisplaySocialContextWithText,
MrPushCopyObjects.TweetRetweetVideoWithTwoDisplaySocialContextsWithText,
MrPushCopyObjects.TweetRetweetVideoWithOneDisplayAndKOtherSocialContextsWithText
),
TweetFavorite -> Set(
MrPushCopyObjects.TweetLikeOneSocialContextWithText,
MrPushCopyObjects.TweetLikeTwoSocialContextWithText,
MrPushCopyObjects.TweetLikeMultipleSocialContextWithText
),
TweetFavoritePhoto -> Set(
MrPushCopyObjects.TweetLikePhotoOneSocialContextWithText,
MrPushCopyObjects.TweetLikePhotoTwoSocialContextWithText,
MrPushCopyObjects.TweetLikePhotoMultipleSocialContextWithText
),
TweetFavoriteVideo -> Set(
MrPushCopyObjects.TweetLikeVideoOneSocialContextWithText,
MrPushCopyObjects.TweetLikeVideoTwoSocialContextWithText,
MrPushCopyObjects.TweetLikeVideoMultipleSocialContextWithText
),
UnreadBadgeCount -> Set(MrPushCopyObjects.UnreadBadgeCount),
InterestBasedTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
InterestBasedPhoto -> Set(MrPushCopyObjects.RecommendedForYouPhoto),
InterestBasedVideo -> Set(MrPushCopyObjects.RecommendedForYouVideo),
UserFollow -> Set(
MrPushCopyObjects.UserFollowWithOneSocialContext,
MrPushCopyObjects.UserFollowWithTwoSocialContext,
MrPushCopyObjects.UserFollowOneDisplayAndKOtherSocialContext
),
HermitUser -> Set(
MrPushCopyObjects.HermitUserWithOneSocialContext,
MrPushCopyObjects.HermitUserWithTwoSocialContext,
MrPushCopyObjects.HermitUserWithOneDisplayAndKOtherSocialContexts
),
TriangularLoopUser -> Set(
MrPushCopyObjects.TriangularLoopUserWithOneSocialContext,
MrPushCopyObjects.TriangularLoopUserWithTwoSocialContexts,
MrPushCopyObjects.TriangularLoopUserOneDisplayAndKotherSocialContext
),
ForwardAddressbookUserFollow -> Set(MrPushCopyObjects.ForwardAddressBookUserFollow),
NewsArticleNewsLanding -> Set(MrPushCopyObjects.NewsArticleNewsLandingCopy),
TopicProofTweet -> Set(MrPushCopyObjects.TopicProofTweet),
UserInterestinTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
UserInterestinPhoto -> Set(MrPushCopyObjects.RecommendedForYouPhoto),
UserInterestinVideo -> Set(MrPushCopyObjects.RecommendedForYouVideo),
TwistlyTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
TwistlyPhoto -> Set(MrPushCopyObjects.RecommendedForYouPhoto),
TwistlyVideo -> Set(MrPushCopyObjects.RecommendedForYouVideo),
ElasticTimelineTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
ElasticTimelinePhoto -> Set(MrPushCopyObjects.RecommendedForYouPhoto),
ElasticTimelineVideo -> Set(MrPushCopyObjects.RecommendedForYouVideo),
ExploreVideoTweet -> Set(MrPushCopyObjects.ExploreVideoTweet),
List -> Set(MrPushCopyObjects.ListRecommendation),
InterestBasedUserFollow -> Set(MrPushCopyObjects.UserFollowInterestBasedCopy),
PastEmailEngagementTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
PastEmailEngagementPhoto -> Set(MrPushCopyObjects.RecommendedForYouPhoto),
PastEmailEngagementVideo -> Set(MrPushCopyObjects.RecommendedForYouVideo),
ExplorePush -> Set(MrPushCopyObjects.ExplorePush),
ConnectTabPush -> Set(MrPushCopyObjects.ConnectTabPush),
ConnectTabWithUserPush -> Set(MrPushCopyObjects.ConnectTabWithUserPush),
AddressBookUploadPush -> Set(MrPushCopyObjects.AddressBookPush),
InterestPickerPush -> Set(MrPushCopyObjects.InterestPickerPush),
CompleteOnboardingPush -> Set(MrPushCopyObjects.CompleteOnboardingPush),
GeoPopTweet -> Set(MrPushCopyObjects.GeoPopPushCopy),
TagSpaceTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
FrsTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
TwhinTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
MrModelingBasedTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
DetopicTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
TweetImpressions -> Set(MrPushCopyObjects.TopTweetImpressions),
TrendTweet -> Set(MrPushCopyObjects.TrendTweet),
ReverseAddressbookTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
ForwardAddressbookTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
SpaceInNetwork -> Set(MrPushCopyObjects.SpaceHost),
SpaceOutOfNetwork -> Set(MrPushCopyObjects.SpaceHost),
SubscribedSearch -> Set(MrPushCopyObjects.SubscribedSearchTweet),
TripGeoTweet -> Set(MrPushCopyObjects.TripGeoTweetPushCopy),
CrowdSearchTweet -> Set(MrPushCopyObjects.RecommendedForYouTweet),
Digest -> Set(MrPushCopyObjects.Digest),
TripHqTweet -> Set(MrPushCopyObjects.TripHqTweetPushCopy)
)
// Static map from a push copy to set of eligible ntab copies
private[cross] val pushcopyToNtabcopy: Map[MRPushCopy, Set[MRNtabCopy]] =
Map[MRPushCopy, Set[MRNtabCopy]](
MrPushCopyObjects.FirstDegreeJustTweetedBoldTitle -> Set(
MrNtabCopyObjects.FirstDegreeTweetRecent),
MrPushCopyObjects.FirstDegreePhotoJustTweetedBoldTitle -> Set(
MrNtabCopyObjects.FirstDegreeTweetRecent
),
MrPushCopyObjects.FirstDegreeVideoJustTweetedBoldTitle -> Set(
MrNtabCopyObjects.FirstDegreeTweetRecent
),
MrPushCopyObjects.TweetRetweetWithOneDisplaySocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetRetweetWithTwoDisplaySocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetRetweetWithOneDisplayAndKOtherSocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TweetRetweetPhotoWithOneDisplaySocialContextWithText -> Set(
MrNtabCopyObjects.TweetRetweetPhotoWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetRetweetPhotoWithTwoDisplaySocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetPhotoWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetRetweetPhotoWithOneDisplayAndKOtherSocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetPhotoWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TweetRetweetVideoWithOneDisplaySocialContextWithText -> Set(
MrNtabCopyObjects.TweetRetweetVideoWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetRetweetVideoWithTwoDisplaySocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetVideoWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetRetweetVideoWithOneDisplayAndKOtherSocialContextsWithText -> Set(
MrNtabCopyObjects.TweetRetweetVideoWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TweetLikeOneSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetLikeTwoSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetLikeMultipleSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TweetLikePhotoOneSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikePhotoWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetLikePhotoTwoSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikePhotoWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetLikePhotoMultipleSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikePhotoWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TweetLikeVideoOneSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeVideoWithOneDisplaySocialContext
),
MrPushCopyObjects.TweetLikeVideoTwoSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeVideoWithTwoDisplaySocialContexts
),
MrPushCopyObjects.TweetLikeVideoMultipleSocialContextWithText -> Set(
MrNtabCopyObjects.TweetLikeVideoWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.UnreadBadgeCount -> Set.empty[MRNtabCopy],
MrPushCopyObjects.RecommendedForYouTweet -> Set(MrNtabCopyObjects.RecommendedForYouCopy),
MrPushCopyObjects.RecommendedForYouPhoto -> Set(MrNtabCopyObjects.RecommendedForYouCopy),
MrPushCopyObjects.RecommendedForYouVideo -> Set(MrNtabCopyObjects.RecommendedForYouCopy),
MrPushCopyObjects.GeoPopPushCopy -> Set(MrNtabCopyObjects.RecommendedForYouCopy),
MrPushCopyObjects.UserFollowWithOneSocialContext -> Set(
MrNtabCopyObjects.UserFollowWithOneDisplaySocialContext
),
MrPushCopyObjects.UserFollowWithTwoSocialContext -> Set(
MrNtabCopyObjects.UserFollowWithTwoDisplaySocialContexts
),
MrPushCopyObjects.UserFollowOneDisplayAndKOtherSocialContext -> Set(
MrNtabCopyObjects.UserFollowWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.HermitUserWithOneSocialContext -> Set(
MrNtabCopyObjects.UserFollowWithOneDisplaySocialContext
),
MrPushCopyObjects.HermitUserWithTwoSocialContext -> Set(
MrNtabCopyObjects.UserFollowWithTwoDisplaySocialContexts
),
MrPushCopyObjects.HermitUserWithOneDisplayAndKOtherSocialContexts -> Set(
MrNtabCopyObjects.UserFollowWithOneDisplayAndKOtherSocialContexts
),
MrPushCopyObjects.TriangularLoopUserWithOneSocialContext -> Set(
MrNtabCopyObjects.TriangularLoopUserWithOneSocialContext
),
MrPushCopyObjects.TriangularLoopUserWithTwoSocialContexts -> Set(
MrNtabCopyObjects.TriangularLoopUserWithTwoSocialContexts
),
MrPushCopyObjects.TriangularLoopUserOneDisplayAndKotherSocialContext -> Set(
MrNtabCopyObjects.TriangularLoopUserOneDisplayAndKOtherSocialContext
),
MrPushCopyObjects.NewsArticleNewsLandingCopy -> Set(
MrNtabCopyObjects.NewsArticleNewsLandingCopy
),
MrPushCopyObjects.UserFollowInterestBasedCopy -> Set(
MrNtabCopyObjects.UserFollowInterestBasedCopy
),
MrPushCopyObjects.ForwardAddressBookUserFollow -> Set(
MrNtabCopyObjects.ForwardAddressBookUserFollow),
MrPushCopyObjects.ConnectTabPush -> Set(
MrNtabCopyObjects.ConnectTabPush
),
MrPushCopyObjects.ExplorePush -> Set.empty[MRNtabCopy],
MrPushCopyObjects.ConnectTabWithUserPush -> Set(
MrNtabCopyObjects.UserFollowInterestBasedCopy),
MrPushCopyObjects.AddressBookPush -> Set(MrNtabCopyObjects.AddressBook),
MrPushCopyObjects.InterestPickerPush -> Set(MrNtabCopyObjects.InterestPicker),
MrPushCopyObjects.CompleteOnboardingPush -> Set(MrNtabCopyObjects.CompleteOnboarding),
MrPushCopyObjects.TopicProofTweet -> Set(MrNtabCopyObjects.TopicProofTweet),
MrPushCopyObjects.TopTweetImpressions -> Set(MrNtabCopyObjects.TopTweetImpressions),
MrPushCopyObjects.TrendTweet -> Set(MrNtabCopyObjects.TrendTweet),
MrPushCopyObjects.SpaceHost -> Set(MrNtabCopyObjects.SpaceHost),
MrPushCopyObjects.SubscribedSearchTweet -> Set(MrNtabCopyObjects.SubscribedSearchTweet),
MrPushCopyObjects.TripGeoTweetPushCopy -> Set(MrNtabCopyObjects.RecommendedForYouCopy),
MrPushCopyObjects.Digest -> Set(MrNtabCopyObjects.Digest),
MrPushCopyObjects.TripHqTweetPushCopy -> Set(MrNtabCopyObjects.HighQualityTweet),
MrPushCopyObjects.ExploreVideoTweet -> Set(MrNtabCopyObjects.ExploreVideoTweet),
MrPushCopyObjects.ListRecommendation -> Set(MrNtabCopyObjects.ListRecommendation),
MrPushCopyObjects.MagicFanoutCreatorSubscription -> Set(
MrNtabCopyObjects.MagicFanoutCreatorSubscription),
MrPushCopyObjects.MagicFanoutNewCreator -> Set(MrNtabCopyObjects.MagicFanoutNewCreator)
)
/**
*
* @param crt - [[CommonRecommendationType]] used for a frigate push notification
*
* @return - Set of [[MRPushCopy]] objects representing push copies eligibile for a
* [[CommonRecommendationType]]
*/
def getPushCopiesFromRectype(crt: CommonRecommendationType): Option[Set[MRPushCopy]] =
rectypeToPushCopy.get(crt)
/**
*
* @param pushcopy - [[MRPushCopy]] object representing a push notification copy
* @return - Set of [[MRNtabCopy]] objects that can be paired with a given [[MRPushCopy]]
*/
def getNtabcopiesFromPushcopy(pushcopy: MRPushCopy): Option[Set[MRNtabCopy]] =
pushcopyToNtabcopy.get(pushcopy)
}

View File

@ -1,41 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base._
import com.twitter.frigate.common.util.MRPushCopy
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.hermit.predicate.Predicate
import com.twitter.util.Future
private[cross] class CopyFilters(statsReceiver: StatsReceiver) {
private val copyPredicates = new CopyPredicates(statsReceiver.scope("copy_predicate"))
def execute(rawCandidate: RawCandidate, pushCopies: Seq[MRPushCopy]): Future[Seq[MRPushCopy]] = {
val candidateCopyPairs: Seq[CandidateCopyPair] =
pushCopies.map(CandidateCopyPair(rawCandidate, _))
val compositePredicate: Predicate[CandidateCopyPair] = rawCandidate match {
case _: F1FirstDegree | _: OutOfNetworkTweetCandidate | _: EventCandidate |
_: TopicProofTweetCandidate | _: ListPushCandidate | _: HermitInterestBasedUserFollow |
_: UserFollowWithoutSocialContextCandidate | _: DiscoverTwitterCandidate |
_: TopTweetImpressionsCandidate | _: TrendTweetCandidate |
_: SubscribedSearchTweetCandidate | _: DigestCandidate =>
copyPredicates.alwaysTruePredicate
case _: SocialContextActions => copyPredicates.displaySocialContextPredicate
case _ => copyPredicates.unrecognizedCandidatePredicate // block unrecognised candidates
}
// apply predicate to all [[MRPushCopy]] objects
val filterResults: Future[Seq[Boolean]] = compositePredicate(candidateCopyPairs)
filterResults.map { results: Seq[Boolean] =>
val seqBuilder = Seq.newBuilder[MRPushCopy]
results.zip(pushCopies).foreach {
case (result, pushCopy) => if (result) seqBuilder += pushCopy
}
seqBuilder.result()
}
}
}

View File

@ -1,36 +0,0 @@
package com.twitter.frigate.pushservice.refresh_handler.cross
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.SocialContextActions
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.hermit.predicate.Predicate
class CopyPredicates(statsReceiver: StatsReceiver) {
val alwaysTruePredicate = Predicate
.from { _: CandidateCopyPair =>
true
}.withStats(statsReceiver.scope("always_true_copy_predicate"))
val unrecognizedCandidatePredicate = alwaysTruePredicate.flip
.withStats(statsReceiver.scope("unrecognized_candidate"))
val displaySocialContextPredicate = Predicate
.from { candidateCopyPair: CandidateCopyPair =>
candidateCopyPair.candidate match {
case candidateWithScActions: RawCandidate with SocialContextActions =>
val socialContextUserIds = candidateWithScActions.socialContextActions.map(_.userId)
val countSocialContext = socialContextUserIds.size
val pushCopy = candidateCopyPair.pushCopy
countSocialContext match {
case 1 => pushCopy.hasOneDisplaySocialContext && !pushCopy.hasOtherSocialContext
case 2 => pushCopy.hasTwoDisplayContext && !pushCopy.hasOtherSocialContext
case c if c > 2 =>
pushCopy.hasOneDisplaySocialContext && pushCopy.hasOtherSocialContext
case _ => false
}
case _ => false
}
}.withStats(statsReceiver.scope("display_social_context_predicate"))
}

View File

@ -1,388 +0,0 @@
package com.twitter.frigate.pushservice.scriber
import com.twitter.bijection.Base64String
import com.twitter.bijection.Injection
import com.twitter.bijection.scrooge.BinaryScalaCodec
import com.twitter.core_workflows.user_model.thriftscala.{UserState => ThriftUserState}
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.finagle.tracing.Trace
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.CandidateResult
import com.twitter.frigate.common.base.Invalid
import com.twitter.frigate.common.base.OK
import com.twitter.frigate.common.base.Result
import com.twitter.frigate.common.rec_types.RecTypes
import com.twitter.frigate.data_pipeline.features_common.PushQualityModelFeatureContext
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams
import com.twitter.frigate.pushservice.params.PushParams
import com.twitter.frigate.scribe.thriftscala.CandidateFilteredOutStep
import com.twitter.frigate.scribe.thriftscala.CandidateRequestInfo
import com.twitter.frigate.scribe.thriftscala.MrRequestScribe
import com.twitter.frigate.scribe.thriftscala.TargetUserInfo
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.frigate.thriftscala.TweetNotification
import com.twitter.frigate.thriftscala.{SocialContextAction => TSocialContextAction}
import com.twitter.logging.Logger
import com.twitter.ml.api.DataRecord
import com.twitter.ml.api.Feature
import com.twitter.ml.api.FeatureType
import com.twitter.ml.api.util.SRichDataRecord
import com.twitter.ml.api.util.ScalaToJavaDataRecordConversions
import com.twitter.nrel.heavyranker.PushPredictionHelper
import com.twitter.util.Future
import com.twitter.util.Time
import java.util.UUID
import scala.collection.mutable
class MrRequestScribeHandler(mrRequestScriberNode: String, stats: StatsReceiver) {
private val mrRequestScribeLogger = Logger(mrRequestScriberNode)
private val mrRequestScribeTargetFilteringStats =
stats.counter("MrRequestScribeHandler_target_filtering")
private val mrRequestScribeCandidateFilteringStats =
stats.counter("MrRequestScribeHandler_candidate_filtering")
private val mrRequestScribeInvalidStats =
stats.counter("MrRequestScribeHandler_invalid_filtering")
private val mrRequestScribeUnsupportedFeatureTypeStats =
stats.counter("MrRequestScribeHandler_unsupported_feature_type")
private val mrRequestScribeNotIncludedFeatureStats =
stats.counter("MrRequestScribeHandler_not_included_features")
private final val MrRequestScribeInjection: Injection[MrRequestScribe, String] = BinaryScalaCodec(
MrRequestScribe
) andThen Injection.connect[Array[Byte], Base64String, String]
/**
*
* @param target : Target user id
* @param result : Result for target filtering
*
* @return
*/
def scribeForTargetFiltering(target: Target, result: Result): Future[Option[MrRequestScribe]] = {
if (target.isLoggedOutUser || !enableTargetFilteringScribing(target)) {
Future.None
} else {
val predicate = result match {
case Invalid(reason) => reason
case _ =>
mrRequestScribeInvalidStats.incr()
throw new IllegalStateException("Invalid reason for Target Filtering " + result)
}
buildScribeThrift(target, predicate, None).map { targetFilteredScribe =>
writeAtTargetFilteringStep(target, targetFilteredScribe)
Some(targetFilteredScribe)
}
}
}
/**
*
* @param target : Target user id
* @param hydratedCandidates : Candidates hydrated with details: impressionId, frigateNotification and source
* @param preRankingFilteredCandidates : Candidates result filtered out at preRanking filtering step
* @param rankedCandidates : Sorted candidates details ranked by ranking step
* @param rerankedCandidates : Sorted candidates details ranked by reranking step
* @param restrictFilteredCandidates : Candidates details filtered out at restrict step
* @param allTakeCandidateResults : Candidates results at take step, include the candidates we take and the candidates filtered out at take step [with different result]
*
* @return
*/
def scribeForCandidateFiltering(
target: Target,
hydratedCandidates: Seq[CandidateDetails[PushCandidate]],
preRankingFilteredCandidates: Seq[CandidateResult[PushCandidate, Result]],
rankedCandidates: Seq[CandidateDetails[PushCandidate]],
rerankedCandidates: Seq[CandidateDetails[PushCandidate]],
restrictFilteredCandidates: Seq[CandidateDetails[PushCandidate]],
allTakeCandidateResults: Seq[CandidateResult[PushCandidate, Result]]
): Future[Seq[MrRequestScribe]] = {
if (target.isLoggedOutUser || target.isEmailUser) {
Future.Nil
} else if (enableCandidateFilteringScribing(target)) {
val hydrateFeature =
target.params(PushFeatureSwitchParams.EnableMrRequestScribingWithFeatureHydrating) ||
target.scribeFeatureForRequestScribe
val candidateRequestInfoSeq = generateCandidatesScribeInfo(
hydratedCandidates,
preRankingFilteredCandidates,
rankedCandidates,
rerankedCandidates,
restrictFilteredCandidates,
allTakeCandidateResults,
isFeatureHydratingEnabled = hydrateFeature
)
val flattenStructure =
target.params(PushFeatureSwitchParams.EnableFlattenMrRequestScribing) || hydrateFeature
candidateRequestInfoSeq.flatMap { candidateRequestInfos =>
if (flattenStructure) {
Future.collect {
candidateRequestInfos.map { candidateRequestInfo =>
buildScribeThrift(target, None, Some(Seq(candidateRequestInfo)))
.map { mrRequestScribe =>
writeAtCandidateFilteringStep(target, mrRequestScribe)
mrRequestScribe
}
}
}
} else {
buildScribeThrift(target, None, Some(candidateRequestInfos))
.map { mrRequestScribe =>
writeAtCandidateFilteringStep(target, mrRequestScribe)
Seq(mrRequestScribe)
}
}
}
} else Future.Nil
}
private def buildScribeThrift(
target: Target,
targetFilteredOutPredicate: Option[String],
candidatesRequestInfo: Option[Seq[CandidateRequestInfo]]
): Future[MrRequestScribe] = {
Future
.join(
target.targetUserState,
generateTargetFeatureScribeInfo(target),
target.targetUser).map {
case (userStateOption, targetFeatureOption, gizmoduckUserOpt) =>
val userState = userStateOption.map(userState => ThriftUserState(userState.id))
val targetFeatures =
targetFeatureOption.map(ScalaToJavaDataRecordConversions.javaDataRecord2ScalaDataRecord)
val traceId = Trace.id.traceId.toLong
MrRequestScribe(
requestId = UUID.randomUUID.toString.replaceAll("-", ""),
scribedTimeMs = Time.now.inMilliseconds,
targetUserId = target.targetId,
targetUserInfo = Some(
TargetUserInfo(
userState,
features = targetFeatures,
userType = gizmoduckUserOpt.map(_.userType))
),
targetFilteredOutPredicate = targetFilteredOutPredicate,
candidates = candidatesRequestInfo,
traceId = Some(traceId)
)
}
}
private def generateTargetFeatureScribeInfo(
target: Target
): Future[Option[DataRecord]] = {
val featureList =
target.params(PushFeatureSwitchParams.TargetLevelFeatureListForMrRequestScribing)
if (featureList.nonEmpty) {
PushPredictionHelper
.getDataRecordFromTargetFeatureMap(
target.targetId,
target.featureMap,
stats
).map { dataRecord =>
val richRecord =
new SRichDataRecord(dataRecord, PushQualityModelFeatureContext.featureContext)
val selectedRecord =
SRichDataRecord(new DataRecord(), PushQualityModelFeatureContext.featureContext)
featureList.map { featureName =>
val feature: Feature[_] = {
try {
PushQualityModelFeatureContext.featureContext.getFeature(featureName)
} catch {
case _: Exception =>
mrRequestScribeNotIncludedFeatureStats.incr()
throw new IllegalStateException(
"Scribing features not included in FeatureContext: " + featureName)
}
}
richRecord.getFeatureValueOpt(feature).foreach { featureVal =>
feature.getFeatureType() match {
case FeatureType.BINARY =>
selectedRecord.setFeatureValue(
feature.asInstanceOf[Feature[Boolean]],
featureVal.asInstanceOf[Boolean])
case FeatureType.CONTINUOUS =>
selectedRecord.setFeatureValue(
feature.asInstanceOf[Feature[Double]],
featureVal.asInstanceOf[Double])
case FeatureType.STRING =>
selectedRecord.setFeatureValue(
feature.asInstanceOf[Feature[String]],
featureVal.asInstanceOf[String])
case FeatureType.DISCRETE =>
selectedRecord.setFeatureValue(
feature.asInstanceOf[Feature[Long]],
featureVal.asInstanceOf[Long])
case _ =>
mrRequestScribeUnsupportedFeatureTypeStats.incr()
}
}
}
Some(selectedRecord.getRecord)
}
} else Future.None
}
private def generateCandidatesScribeInfo(
hydratedCandidates: Seq[CandidateDetails[PushCandidate]],
preRankingFilteredCandidates: Seq[CandidateResult[PushCandidate, Result]],
rankedCandidates: Seq[CandidateDetails[PushCandidate]],
rerankedCandidates: Seq[CandidateDetails[PushCandidate]],
restrictFilteredCandidates: Seq[CandidateDetails[PushCandidate]],
allTakeCandidateResults: Seq[CandidateResult[PushCandidate, Result]],
isFeatureHydratingEnabled: Boolean
): Future[Seq[CandidateRequestInfo]] = {
val candidatesMap = new mutable.HashMap[String, CandidateRequestInfo]
hydratedCandidates.foreach { hydratedCandidate =>
val frgNotif = hydratedCandidate.candidate.frigateNotification
val simplifiedTweetNotificationOpt = frgNotif.tweetNotification.map { tweetNotification =>
TweetNotification(
tweetNotification.tweetId,
Seq.empty[TSocialContextAction],
tweetNotification.tweetAuthorId)
}
val simplifiedFrigateNotification = FrigateNotification(
frgNotif.commonRecommendationType,
frgNotif.notificationDisplayLocation,
tweetNotification = simplifiedTweetNotificationOpt
)
candidatesMap(hydratedCandidate.candidate.impressionId) = CandidateRequestInfo(
candidateId = "",
candidateSource = hydratedCandidate.source.substring(
0,
Math.min(6, hydratedCandidate.source.length)
),
frigateNotification = Some(simplifiedFrigateNotification),
modelScore = None,
rankPosition = None,
rerankPosition = None,
features = None,
isSent = Some(false)
)
}
preRankingFilteredCandidates.foreach { preRankingFilteredCandidateResult =>
candidatesMap(preRankingFilteredCandidateResult.candidate.impressionId) =
candidatesMap(preRankingFilteredCandidateResult.candidate.impressionId)
.copy(
candidateFilteredOutPredicate = preRankingFilteredCandidateResult.result match {
case Invalid(reason) => reason
case _ => {
mrRequestScribeInvalidStats.incr()
throw new IllegalStateException(
"Invalid reason for Candidate Filtering " + preRankingFilteredCandidateResult.result)
}
},
candidateFilteredOutStep = Some(CandidateFilteredOutStep.PreRankFiltering)
)
}
for {
_ <- Future.collectToTry {
rankedCandidates.zipWithIndex.map {
case (rankedCandidateDetail, index) =>
val modelScoresFut = {
val crt = rankedCandidateDetail.candidate.commonRecType
if (RecTypes.notEligibleForModelScoreTracking.contains(crt)) Future.None
else rankedCandidateDetail.candidate.modelScores.map(Some(_))
}
modelScoresFut.map { modelScores =>
candidatesMap(rankedCandidateDetail.candidate.impressionId) =
candidatesMap(rankedCandidateDetail.candidate.impressionId).copy(
rankPosition = Some(index),
modelScore = modelScores
)
}
}
}
_ = rerankedCandidates.zipWithIndex.foreach {
case (rerankedCandidateDetail, index) => {
candidatesMap(rerankedCandidateDetail.candidate.impressionId) =
candidatesMap(rerankedCandidateDetail.candidate.impressionId).copy(
rerankPosition = Some(index)
)
}
}
_ <- Future.collectToTry {
rerankedCandidates.map { rerankedCandidateDetail =>
if (isFeatureHydratingEnabled) {
PushPredictionHelper
.getDataRecord(
rerankedCandidateDetail.candidate.target.targetHydrationContext,
rerankedCandidateDetail.candidate.target.featureMap,
rerankedCandidateDetail.candidate.candidateHydrationContext,
rerankedCandidateDetail.candidate.candidateFeatureMap(),
stats
).map { features =>
candidatesMap(rerankedCandidateDetail.candidate.impressionId) =
candidatesMap(rerankedCandidateDetail.candidate.impressionId).copy(
features = Some(
ScalaToJavaDataRecordConversions.javaDataRecord2ScalaDataRecord(features))
)
}
} else Future.Unit
}
}
_ = restrictFilteredCandidates.foreach { restrictFilteredCandidateDetatil =>
candidatesMap(restrictFilteredCandidateDetatil.candidate.impressionId) =
candidatesMap(restrictFilteredCandidateDetatil.candidate.impressionId)
.copy(candidateFilteredOutStep = Some(CandidateFilteredOutStep.Restrict))
}
_ = allTakeCandidateResults.foreach { allTakeCandidateResult =>
allTakeCandidateResult.result match {
case OK =>
candidatesMap(allTakeCandidateResult.candidate.impressionId) =
candidatesMap(allTakeCandidateResult.candidate.impressionId).copy(isSent = Some(true))
case Invalid(reason) =>
candidatesMap(allTakeCandidateResult.candidate.impressionId) =
candidatesMap(allTakeCandidateResult.candidate.impressionId).copy(
candidateFilteredOutPredicate = reason,
candidateFilteredOutStep = Some(CandidateFilteredOutStep.PostRankFiltering))
case _ =>
mrRequestScribeInvalidStats.incr()
throw new IllegalStateException(
"Invalid reason for Candidate Filtering " + allTakeCandidateResult.result)
}
}
} yield candidatesMap.values.toSeq
}
private def enableTargetFilteringScribing(target: Target): Boolean = {
target.params(PushParams.EnableMrRequestScribing) && target.params(
PushFeatureSwitchParams.EnableMrRequestScribingForTargetFiltering)
}
private def enableCandidateFilteringScribing(target: Target): Boolean = {
target.params(PushParams.EnableMrRequestScribing) && target.params(
PushFeatureSwitchParams.EnableMrRequestScribingForCandidateFiltering)
}
private def writeAtTargetFilteringStep(target: Target, mrRequestScribe: MrRequestScribe) = {
logToScribe(mrRequestScribe)
mrRequestScribeTargetFilteringStats.incr()
}
private def writeAtCandidateFilteringStep(target: Target, mrRequestScribe: MrRequestScribe) = {
logToScribe(mrRequestScribe)
mrRequestScribeCandidateFilteringStats.incr()
}
private def logToScribe(mrRequestScribe: MrRequestScribe): Unit = {
val logEntry: String = MrRequestScribeInjection(mrRequestScribe)
mrRequestScribeLogger.info(logEntry)
}
}

View File

@ -1,250 +0,0 @@
package com.twitter.frigate.pushservice.send_handler
import com.twitter.finagle.stats.BroadcastStatsReceiver
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base.CandidateDetails
import com.twitter.frigate.common.base.CandidateFilteringOnlyFlow
import com.twitter.frigate.common.base.CandidateResult
import com.twitter.frigate.common.base.FeatureMap
import com.twitter.frigate.common.base.OK
import com.twitter.frigate.common.base.Response
import com.twitter.frigate.common.base.Result
import com.twitter.frigate.common.base.Stats.track
import com.twitter.frigate.common.config.CommonConstants
import com.twitter.frigate.common.logger.MRLogger
import com.twitter.frigate.common.rec_types.RecTypes
import com.twitter.frigate.common.util.InvalidRequestException
import com.twitter.frigate.common.util.MrNtabCopyObjects
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.config.Config
import com.twitter.frigate.pushservice.ml.HydrationContextBuilder
import com.twitter.frigate.pushservice.params.PushFeatureSwitchParams.EnableMagicFanoutNewsForYouNtabCopy
import com.twitter.frigate.pushservice.scriber.MrRequestScribeHandler
import com.twitter.frigate.pushservice.send_handler.generator.PushRequestToCandidate
import com.twitter.frigate.pushservice.take.SendHandlerNotifier
import com.twitter.frigate.pushservice.take.candidate_validator.SendHandlerPostCandidateValidator
import com.twitter.frigate.pushservice.take.candidate_validator.SendHandlerPreCandidateValidator
import com.twitter.frigate.pushservice.target.PushTargetUserBuilder
import com.twitter.frigate.pushservice.util.ResponseStatsTrackUtils.trackStatsForResponseToRequest
import com.twitter.frigate.pushservice.util.SendHandlerPredicateUtil
import com.twitter.frigate.pushservice.thriftscala.PushRequest
import com.twitter.frigate.pushservice.thriftscala.PushRequestScribe
import com.twitter.frigate.pushservice.thriftscala.PushResponse
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.nrel.heavyranker.FeatureHydrator
import com.twitter.util._
/**
* A handler for sending PushRequests
*/
class SendHandler(
pushTargetUserBuilder: PushTargetUserBuilder,
preCandidateValidator: SendHandlerPreCandidateValidator,
postCandidateValidator: SendHandlerPostCandidateValidator,
sendHandlerNotifier: SendHandlerNotifier,
candidateHydrator: SendHandlerPushCandidateHydrator,
featureHydrator: FeatureHydrator,
sendHandlerPredicateUtil: SendHandlerPredicateUtil,
mrRequestScriberNode: String
)(
implicit val statsReceiver: StatsReceiver,
implicit val config: Config)
extends CandidateFilteringOnlyFlow[Target, RawCandidate, PushCandidate] {
implicit private val timer: Timer = new JavaTimer(true)
val stats = statsReceiver.scope("SendHandler")
val log = MRLogger("SendHandler")
private val buildTargetStats = stats.scope("build_target")
private val candidateHydrationLatency: Stat =
stats.stat("candidateHydrationLatency")
private val candidatePreValidatorLatency: Stat =
stats.stat("candidatePreValidatorLatency")
private val candidatePostValidatorLatency: Stat =
stats.stat("candidatePostValidatorLatency")
private val featureHydrationLatency: StatsReceiver =
stats.scope("featureHydrationLatency")
private val mrRequestScribeHandler =
new MrRequestScribeHandler(mrRequestScriberNode, stats.scope("mr_request_scribe"))
def apply(request: PushRequest): Future[PushResponse] = {
val receivers = Seq(
stats,
stats.scope(request.notification.commonRecommendationType.toString)
)
val bStats = BroadcastStatsReceiver(receivers)
bStats.counter("requests").incr()
Stat
.timeFuture(bStats.stat("latency"))(
process(request).raiseWithin(CommonConstants.maxPushRequestDuration))
.onSuccess {
case (pushResp, rawCandidate) =>
trackStatsForResponseToRequest(
rawCandidate.commonRecType,
rawCandidate.target,
pushResp,
receivers)(statsReceiver)
if (!request.context.exists(_.darkWrite.contains(true))) {
config.requestScribe(PushRequestScribe(request, pushResp))
}
}
.onFailure { ex =>
bStats.counter("failures").incr()
bStats.scope("failures").counter(ex.getClass.getCanonicalName).incr()
}
.map {
case (pushResp, _) => pushResp
}
}
private def process(request: PushRequest): Future[(PushResponse, RawCandidate)] = {
val recType = request.notification.commonRecommendationType
track(buildTargetStats)(
pushTargetUserBuilder
.buildTarget(
request.userId,
request.context
)
).flatMap { targetUser =>
val responseWithScribedInfo = request.context.exists { context =>
context.responseWithScribedInfo.contains(true)
}
val newRequest =
if (request.notification.commonRecommendationType == CommonRecommendationType.MagicFanoutNewsEvent &&
targetUser.params(EnableMagicFanoutNewsForYouNtabCopy)) {
val newNotification = request.notification.copy(ntabCopyId =
Some(MrNtabCopyObjects.MagicFanoutNewsForYouCopy.copyId))
request.copy(notification = newNotification)
} else request
if (RecTypes.isSendHandlerType(recType) || newRequest.context.exists(
_.allowCRT.contains(true))) {
val rawCandidateFut = PushRequestToCandidate.generatePushCandidate(
newRequest.notification,
targetUser
)
rawCandidateFut.flatMap { rawCandidate =>
val pushResponse = process(targetUser, Seq(rawCandidate)).flatMap {
sendHandlerNotifier.checkResponseAndNotify(_, responseWithScribedInfo)
}
pushResponse.map { pushResponse =>
(pushResponse, rawCandidate)
}
}
} else {
Future.exception(InvalidRequestException(s"${recType.name} not supported in SendHandler"))
}
}
}
private def hydrateFeatures(
candidateDetails: Seq[CandidateDetails[PushCandidate]],
target: Target,
): Future[Seq[CandidateDetails[PushCandidate]]] = {
candidateDetails.headOption match {
case Some(candidateDetail)
if RecTypes.notEligibleForModelScoreTracking(candidateDetail.candidate.commonRecType) =>
Future.value(candidateDetails)
case Some(candidateDetail) =>
val hydrationContextFut = HydrationContextBuilder.build(candidateDetail.candidate)
hydrationContextFut.flatMap { hc =>
featureHydrator
.hydrateCandidate(Seq(hc), target.mrRequestContextForFeatureStore)
.map { hydrationResult =>
val features = hydrationResult.getOrElse(hc, FeatureMap())
candidateDetail.candidate.mergeFeatures(features)
candidateDetails
}
}
case _ => Future.Nil
}
}
override def process(
target: Target,
externalCandidates: Seq[RawCandidate]
): Future[Response[PushCandidate, Result]] = {
val candidate = externalCandidates.map(CandidateDetails(_, "realtime"))
for {
hydratedCandidatesWithCopy <- hydrateCandidates(candidate)
(candidates, preHydrationFilteredCandidates) <- track(filterStats)(
filter(target, hydratedCandidatesWithCopy)
)
featureHydratedCandidates <-
track(featureHydrationLatency)(hydrateFeatures(candidates, target))
allTakeCandidateResults <- track(takeStats)(
take(target, featureHydratedCandidates, desiredCandidateCount(target))
)
_ <- mrRequestScribeHandler.scribeForCandidateFiltering(
target = target,
hydratedCandidates = hydratedCandidatesWithCopy,
preRankingFilteredCandidates = preHydrationFilteredCandidates,
rankedCandidates = featureHydratedCandidates,
rerankedCandidates = Seq.empty,
restrictFilteredCandidates = Seq.empty, // no restrict step
allTakeCandidateResults = allTakeCandidateResults
)
} yield {
/**
* We combine the results for all filtering steps and pass on in sequence to next step
*
* This is done to ensure the filtering reason for the candidate from multiple levels of
* filtering is carried all the way until [[PushResponse]] is built and returned from
* frigate-pushservice-send
*/
Response(OK, allTakeCandidateResults ++ preHydrationFilteredCandidates)
}
}
override def hydrateCandidates(
candidates: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
Stat.timeFuture(candidateHydrationLatency)(candidateHydrator(candidates))
}
// Filter Step - pre-predicates and app specific predicates
override def filter(
target: Target,
hydratedCandidatesDetails: Seq[CandidateDetails[PushCandidate]]
): Future[
(Seq[CandidateDetails[PushCandidate]], Seq[CandidateResult[PushCandidate, Result]])
] = {
Stat.timeFuture(candidatePreValidatorLatency)(
sendHandlerPredicateUtil.preValidationForCandidate(
hydratedCandidatesDetails,
preCandidateValidator
))
}
// Post Validation - Take step
override def validCandidates(
target: Target,
candidates: Seq[PushCandidate]
): Future[Seq[Result]] = {
Stat.timeFuture(candidatePostValidatorLatency)(Future.collect(candidates.map { candidate =>
sendHandlerPredicateUtil
.postValidationForCandidate(candidate, postCandidateValidator)
.map(res => res.result)
}))
}
}

View File

@ -1,184 +0,0 @@
package com.twitter.frigate.pushservice.send_handler
import com.twitter.escherbird.metadata.thriftscala.EntityMegadata
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.base._
import com.twitter.frigate.common.store.interests.InterestsLookupRequestWithContext
import com.twitter.frigate.common.util.MrNtabCopyObjects
import com.twitter.frigate.common.util.MrPushCopyObjects
import com.twitter.frigate.magic_events.thriftscala.FanoutEvent
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.ml.PushMLModelScorer
import com.twitter.frigate.pushservice.model.candidate.CopyIds
import com.twitter.frigate.pushservice.store.EventRequest
import com.twitter.frigate.pushservice.store.UttEntityHydrationStore
import com.twitter.frigate.pushservice.util.CandidateHydrationUtil._
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.gizmoduck.thriftscala.User
import com.twitter.hermit.store.semantic_core.SemanticEntityForQuery
import com.twitter.interests.thriftscala.UserInterests
import com.twitter.livevideo.timeline.domain.v2.{Event => LiveEvent}
import com.twitter.simclusters_v2.thriftscala.SimClustersInferredEntities
import com.twitter.storehaus.ReadableStore
import com.twitter.strato.client.UserId
import com.twitter.ubs.thriftscala.AudioSpace
import com.twitter.util.Future
case class SendHandlerPushCandidateHydrator(
lexServiceStore: ReadableStore[EventRequest, LiveEvent],
fanoutMetadataStore: ReadableStore[(Long, Long), FanoutEvent],
semanticCoreMegadataStore: ReadableStore[SemanticEntityForQuery, EntityMegadata],
safeUserStore: ReadableStore[Long, User],
simClusterToEntityStore: ReadableStore[Int, SimClustersInferredEntities],
audioSpaceStore: ReadableStore[String, AudioSpace],
interestsLookupStore: ReadableStore[InterestsLookupRequestWithContext, UserInterests],
uttEntityHydrationStore: UttEntityHydrationStore,
superFollowCreatorTweetCountStore: ReadableStore[UserId, Int]
)(
implicit statsReceiver: StatsReceiver,
implicit val weightedOpenOrNtabClickModelScorer: PushMLModelScorer) {
lazy val candidateWithCopyNumStat = statsReceiver.stat("candidate_with_copy_num")
lazy val hydratedCandidateStat = statsReceiver.scope("hydrated_candidates")
def updateCandidates(
candidateDetails: Seq[CandidateDetails[RawCandidate]],
): Future[Seq[CandidateDetails[PushCandidate]]] = {
Future.collect {
candidateDetails.map { candidateDetail =>
val pushCandidate = candidateDetail.candidate
val copyIds = getCopyIdsByCRT(pushCandidate.commonRecType)
val hydratedCandidateFut = pushCandidate match {
case magicFanoutNewsEventCandidate: MagicFanoutNewsEventCandidate =>
getHydratedCandidateForMagicFanoutNewsEvent(
magicFanoutNewsEventCandidate,
copyIds,
lexServiceStore,
fanoutMetadataStore,
semanticCoreMegadataStore,
simClusterToEntityStore,
interestsLookupStore,
uttEntityHydrationStore
)
case scheduledSpaceSubscriberCandidate: ScheduledSpaceSubscriberCandidate =>
getHydratedCandidateForScheduledSpaceSubscriber(
scheduledSpaceSubscriberCandidate,
safeUserStore,
copyIds,
audioSpaceStore
)
case scheduledSpaceSpeakerCandidate: ScheduledSpaceSpeakerCandidate =>
getHydratedCandidateForScheduledSpaceSpeaker(
scheduledSpaceSpeakerCandidate,
safeUserStore,
copyIds,
audioSpaceStore
)
case magicFanoutSportsEventCandidate: MagicFanoutSportsEventCandidate with MagicFanoutSportsScoreInformation =>
getHydratedCandidateForMagicFanoutSportsEvent(
magicFanoutSportsEventCandidate,
copyIds,
lexServiceStore,
fanoutMetadataStore,
semanticCoreMegadataStore,
interestsLookupStore,
uttEntityHydrationStore
)
case magicFanoutProductLaunchCandidate: MagicFanoutProductLaunchCandidate =>
getHydratedCandidateForMagicFanoutProductLaunch(
magicFanoutProductLaunchCandidate,
copyIds)
case creatorEventCandidate: MagicFanoutCreatorEventCandidate =>
getHydratedCandidateForMagicFanoutCreatorEvent(
creatorEventCandidate,
safeUserStore,
copyIds,
superFollowCreatorTweetCountStore)
case _ =>
throw new IllegalArgumentException("Incorrect candidate type when update candidates")
}
hydratedCandidateFut.map { hydratedCandidate =>
hydratedCandidateStat.counter(hydratedCandidate.commonRecType.name).incr()
CandidateDetails(
hydratedCandidate,
source = candidateDetail.source
)
}
}
}
}
private def getCopyIdsByCRT(crt: CommonRecommendationType): CopyIds = {
crt match {
case CommonRecommendationType.MagicFanoutNewsEvent =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.MagicFanoutNewsPushCopy.copyId),
ntabCopyId = Some(MrNtabCopyObjects.MagicFanoutNewsForYouCopy.copyId),
aggregationId = None
)
case CommonRecommendationType.ScheduledSpaceSubscriber =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.ScheduledSpaceSubscriber.copyId),
ntabCopyId = Some(MrNtabCopyObjects.ScheduledSpaceSubscriber.copyId),
aggregationId = None
)
case CommonRecommendationType.ScheduledSpaceSpeaker =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.ScheduledSpaceSpeaker.copyId),
ntabCopyId = Some(MrNtabCopyObjects.ScheduledSpaceSpeakerNow.copyId),
aggregationId = None
)
case CommonRecommendationType.SpaceSpeaker =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.SpaceSpeaker.copyId),
ntabCopyId = Some(MrNtabCopyObjects.SpaceSpeaker.copyId),
aggregationId = None
)
case CommonRecommendationType.SpaceHost =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.SpaceHost.copyId),
ntabCopyId = Some(MrNtabCopyObjects.SpaceHost.copyId),
aggregationId = None
)
case CommonRecommendationType.MagicFanoutSportsEvent =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.MagicFanoutSportsPushCopy.copyId),
ntabCopyId = Some(MrNtabCopyObjects.MagicFanoutSportsCopy.copyId),
aggregationId = None
)
case CommonRecommendationType.MagicFanoutProductLaunch =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.MagicFanoutProductLaunch.copyId),
ntabCopyId = Some(MrNtabCopyObjects.ProductLaunch.copyId),
aggregationId = None
)
case CommonRecommendationType.CreatorSubscriber =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.MagicFanoutCreatorSubscription.copyId),
ntabCopyId = Some(MrNtabCopyObjects.MagicFanoutCreatorSubscription.copyId),
aggregationId = None
)
case CommonRecommendationType.NewCreator =>
CopyIds(
pushCopyId = Some(MrPushCopyObjects.MagicFanoutNewCreator.copyId),
ntabCopyId = Some(MrNtabCopyObjects.MagicFanoutNewCreator.copyId),
aggregationId = None
)
case _ =>
throw new IllegalArgumentException("Incorrect candidate type when fetch copy ids")
}
}
def apply(
candidateDetails: Seq[CandidateDetails[RawCandidate]]
): Future[Seq[CandidateDetails[PushCandidate]]] = {
updateCandidates(candidateDetails)
}
}

View File

@ -1,17 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.util.Future
trait CandidateGenerator {
/**
* Build RawCandidate from FrigateNotification
* @param target
* @param frigateNotification
* @return RawCandidate
*/
def getCandidate(target: Target, frigateNotification: FrigateNotification): Future[RawCandidate]
}

View File

@ -1,70 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.common.base.MagicFanoutCreatorEventCandidate
import com.twitter.frigate.magic_events.thriftscala.CreatorFanoutType
import com.twitter.frigate.magic_events.thriftscala.MagicEventsReason
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.model.PushTypes
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.util.Future
object MagicFanoutCreatorEventCandidateGenerator extends CandidateGenerator {
override def getCandidate(
targetUser: PushTypes.Target,
notification: FrigateNotification
): Future[PushTypes.RawCandidate] = {
require(
notification.commonRecommendationType == CommonRecommendationType.CreatorSubscriber || notification.commonRecommendationType == CommonRecommendationType.NewCreator,
"MagicFanoutCreatorEvent: unexpected CRT " + notification.commonRecommendationType
)
require(
notification.creatorSubscriptionNotification.isDefined,
"MagicFanoutCreatorEvent: creatorSubscriptionNotification is not defined")
require(
notification.creatorSubscriptionNotification.exists(_.magicFanoutPushId.isDefined),
"MagicFanoutCreatorEvent: magicFanoutPushId is not defined")
require(
notification.creatorSubscriptionNotification.exists(_.fanoutReasons.isDefined),
"MagicFanoutCreatorEvent: fanoutReasons is not defined")
require(
notification.creatorSubscriptionNotification.exists(_.creatorId.isDefined),
"MagicFanoutCreatorEvent: creatorId is not defined")
if (notification.commonRecommendationType == CommonRecommendationType.CreatorSubscriber) {
require(
notification.creatorSubscriptionNotification
.exists(_.subscriberId.isDefined),
"MagicFanoutCreatorEvent: subscriber id is not defined"
)
}
val creatorSubscriptionNotification = notification.creatorSubscriptionNotification.get
val candidate = new RawCandidate with MagicFanoutCreatorEventCandidate {
override val target: Target = targetUser
override val pushId: Long =
creatorSubscriptionNotification.magicFanoutPushId.get
override val candidateMagicEventsReasons: Seq[MagicEventsReason] =
creatorSubscriptionNotification.fanoutReasons.get
override val creatorFanoutType: CreatorFanoutType =
creatorSubscriptionNotification.creatorFanoutType
override val commonRecType: CommonRecommendationType =
notification.commonRecommendationType
override val frigateNotification: FrigateNotification = notification
override val subscriberId: Option[Long] = creatorSubscriptionNotification.subscriberId
override val creatorId: Long = creatorSubscriptionNotification.creatorId.get
}
Future.value(candidate)
}
}

View File

@ -1,57 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.common.base.MagicFanoutNewsEventCandidate
import com.twitter.frigate.magic_events.thriftscala.MagicEventsReason
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.frigate.thriftscala.MagicFanoutEventNotificationDetails
import com.twitter.util.Future
object MagicFanoutNewsEventCandidateGenerator extends CandidateGenerator {
override def getCandidate(
targetUser: Target,
notification: FrigateNotification
): Future[RawCandidate] = {
/**
* frigateNotification recommendation type should be [[CommonRecommendationType.MagicFanoutNewsEvent]]
* AND pushId field should be set
**/
require(
notification.commonRecommendationType == CommonRecommendationType.MagicFanoutNewsEvent,
"MagicFanoutNewsEvent: unexpected CRT " + notification.commonRecommendationType
)
require(
notification.magicFanoutEventNotification.exists(_.pushId.isDefined),
"MagicFanoutNewsEvent: pushId is not defined")
val magicFanoutEventNotification = notification.magicFanoutEventNotification.get
val candidate = new RawCandidate with MagicFanoutNewsEventCandidate {
override val target: Target = targetUser
override val eventId: Long = magicFanoutEventNotification.eventId
override val pushId: Long = magicFanoutEventNotification.pushId.get
override val candidateMagicEventsReasons: Seq[MagicEventsReason] =
magicFanoutEventNotification.eventReasons.getOrElse(Seq.empty)
override val momentId: Option[Long] = magicFanoutEventNotification.momentId
override val eventLanguage: Option[String] = magicFanoutEventNotification.eventLanguage
override val details: Option[MagicFanoutEventNotificationDetails] =
magicFanoutEventNotification.details
override val frigateNotification: FrigateNotification = notification
}
Future.value(candidate)
}
}

View File

@ -1,54 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.common.base.MagicFanoutProductLaunchCandidate
import com.twitter.frigate.magic_events.thriftscala.MagicEventsReason
import com.twitter.frigate.magic_events.thriftscala.ProductType
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.model.PushTypes
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.util.Future
object MagicFanoutProductLaunchCandidateGenerator extends CandidateGenerator {
override def getCandidate(
targetUser: PushTypes.Target,
notification: FrigateNotification
): Future[PushTypes.RawCandidate] = {
require(
notification.commonRecommendationType == CommonRecommendationType.MagicFanoutProductLaunch,
"MagicFanoutProductLaunch: unexpected CRT " + notification.commonRecommendationType
)
require(
notification.magicFanoutProductLaunchNotification.isDefined,
"MagicFanoutProductLaunch: magicFanoutProductLaunchNotification is not defined")
require(
notification.magicFanoutProductLaunchNotification.exists(_.magicFanoutPushId.isDefined),
"MagicFanoutProductLaunch: magicFanoutPushId is not defined")
require(
notification.magicFanoutProductLaunchNotification.exists(_.fanoutReasons.isDefined),
"MagicFanoutProductLaunch: fanoutReasons is not defined")
val magicFanoutProductLaunchNotification = notification.magicFanoutProductLaunchNotification.get
val candidate = new RawCandidate with MagicFanoutProductLaunchCandidate {
override val target: Target = targetUser
override val pushId: Long =
magicFanoutProductLaunchNotification.magicFanoutPushId.get
override val candidateMagicEventsReasons: Seq[MagicEventsReason] =
magicFanoutProductLaunchNotification.fanoutReasons.get
override val productLaunchType: ProductType =
magicFanoutProductLaunchNotification.productLaunchType
override val frigateNotification: FrigateNotification = notification
}
Future.value(candidate)
}
}

View File

@ -1,153 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.datatools.entityservice.entities.sports.thriftscala.BaseballGameLiveUpdate
import com.twitter.datatools.entityservice.entities.sports.thriftscala.BasketballGameLiveUpdate
import com.twitter.datatools.entityservice.entities.sports.thriftscala.CricketMatchLiveUpdate
import com.twitter.datatools.entityservice.entities.sports.thriftscala.NflFootballGameLiveUpdate
import com.twitter.datatools.entityservice.entities.sports.thriftscala.SoccerMatchLiveUpdate
import com.twitter.escherbird.common.thriftscala.Domains
import com.twitter.escherbird.common.thriftscala.QualifiedId
import com.twitter.escherbird.metadata.thriftscala.EntityMegadata
import com.twitter.frigate.common.base.BaseGameScore
import com.twitter.frigate.common.base.MagicFanoutSportsEventCandidate
import com.twitter.frigate.common.base.MagicFanoutSportsScoreInformation
import com.twitter.frigate.common.base.TeamInfo
import com.twitter.frigate.magic_events.thriftscala.MagicEventsReason
import com.twitter.frigate.pushservice.exception.InvalidSportDomainException
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.params.PushConstants
import com.twitter.frigate.pushservice.predicate.magic_fanout.MagicFanoutSportsUtil
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.frigate.thriftscala.MagicFanoutEventNotificationDetails
import com.twitter.hermit.store.semantic_core.SemanticEntityForQuery
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
object MagicFanoutSportsEventCandidateGenerator {
final def getCandidate(
targetUser: Target,
notification: FrigateNotification,
basketballGameScoreStore: ReadableStore[QualifiedId, BasketballGameLiveUpdate],
baseballGameScoreStore: ReadableStore[QualifiedId, BaseballGameLiveUpdate],
cricketMatchScoreStore: ReadableStore[QualifiedId, CricketMatchLiveUpdate],
soccerMatchScoreStore: ReadableStore[QualifiedId, SoccerMatchLiveUpdate],
nflGameScoreStore: ReadableStore[QualifiedId, NflFootballGameLiveUpdate],
semanticCoreMegadataStore: ReadableStore[SemanticEntityForQuery, EntityMegadata],
): Future[RawCandidate] = {
/**
* frigateNotification recommendation type should be [[CommonRecommendationType.MagicFanoutSportsEvent]]
* AND pushId field should be set
*
* */
require(
notification.commonRecommendationType == CommonRecommendationType.MagicFanoutSportsEvent,
"MagicFanoutSports: unexpected CRT " + notification.commonRecommendationType
)
require(
notification.magicFanoutEventNotification.exists(_.pushId.isDefined),
"MagicFanoutSportsEvent: pushId is not defined")
val magicFanoutEventNotification = notification.magicFanoutEventNotification.get
val eventId = magicFanoutEventNotification.eventId
val _isScoreUpdate = magicFanoutEventNotification.isScoreUpdate.getOrElse(false)
val gameScoresFut: Future[Option[BaseGameScore]] = {
if (_isScoreUpdate) {
semanticCoreMegadataStore
.get(SemanticEntityForQuery(PushConstants.SportsEventDomainId, eventId))
.flatMap {
case Some(megadata) =>
if (megadata.domains.contains(Domains.BasketballGame)) {
basketballGameScoreStore
.get(QualifiedId(Domains.BasketballGame.value, eventId)).map {
case Some(game) if game.status.isDefined =>
val status = game.status.get
MagicFanoutSportsUtil.transformToGameScore(game.score, status)
case _ => None
}
} else if (megadata.domains.contains(Domains.BaseballGame)) {
baseballGameScoreStore
.get(QualifiedId(Domains.BaseballGame.value, eventId)).map {
case Some(game) if game.status.isDefined =>
val status = game.status.get
MagicFanoutSportsUtil.transformToGameScore(game.runs, status)
case _ => None
}
} else if (megadata.domains.contains(Domains.NflFootballGame)) {
nflGameScoreStore
.get(QualifiedId(Domains.NflFootballGame.value, eventId)).map {
case Some(game) if game.status.isDefined =>
val nflScore = MagicFanoutSportsUtil.transformNFLGameScore(game)
nflScore
case _ => None
}
} else if (megadata.domains.contains(Domains.SoccerMatch)) {
soccerMatchScoreStore
.get(QualifiedId(Domains.SoccerMatch.value, eventId)).map {
case Some(game) if game.status.isDefined =>
val soccerScore = MagicFanoutSportsUtil.transformSoccerGameScore(game)
soccerScore
case _ => None
}
} else {
// The domains are not in our list of supported sports
throw new InvalidSportDomainException(
s"Domain for entity ${eventId} is not supported")
}
case _ => Future.None
}
} else Future.None
}
val homeTeamInfoFut: Future[Option[TeamInfo]] = gameScoresFut.flatMap {
case Some(gameScore) =>
MagicFanoutSportsUtil.getTeamInfo(gameScore.home, semanticCoreMegadataStore)
case _ => Future.None
}
val awayTeamInfoFut: Future[Option[TeamInfo]] = gameScoresFut.flatMap {
case Some(gameScore) =>
MagicFanoutSportsUtil.getTeamInfo(gameScore.away, semanticCoreMegadataStore)
case _ => Future.None
}
val candidate = new RawCandidate
with MagicFanoutSportsEventCandidate
with MagicFanoutSportsScoreInformation {
override val target: Target = targetUser
override val eventId: Long = magicFanoutEventNotification.eventId
override val pushId: Long = magicFanoutEventNotification.pushId.get
override val candidateMagicEventsReasons: Seq[MagicEventsReason] =
magicFanoutEventNotification.eventReasons.getOrElse(Seq.empty)
override val momentId: Option[Long] = magicFanoutEventNotification.momentId
override val eventLanguage: Option[String] = magicFanoutEventNotification.eventLanguage
override val details: Option[MagicFanoutEventNotificationDetails] =
magicFanoutEventNotification.details
override val frigateNotification: FrigateNotification = notification
override val homeTeamInfo: Future[Option[TeamInfo]] = homeTeamInfoFut
override val awayTeamInfo: Future[Option[TeamInfo]] = awayTeamInfoFut
override val gameScores: Future[Option[BaseGameScore]] = gameScoresFut
override val isScoreUpdate: Boolean = _isScoreUpdate
}
Future.value(candidate)
}
}

View File

@ -1,49 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.pushservice.config.Config
import com.twitter.frigate.pushservice.exception.UnsupportedCrtException
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.frigate.thriftscala.{CommonRecommendationType => CRT}
import com.twitter.util.Future
object PushRequestToCandidate {
final def generatePushCandidate(
frigateNotification: FrigateNotification,
target: Target
)(
implicit config: Config
): Future[RawCandidate] = {
val candidateGenerator: (Target, FrigateNotification) => Future[RawCandidate] = {
frigateNotification.commonRecommendationType match {
case CRT.MagicFanoutNewsEvent => MagicFanoutNewsEventCandidateGenerator.getCandidate
case CRT.ScheduledSpaceSubscriber => ScheduledSpaceSubscriberCandidateGenerator.getCandidate
case CRT.ScheduledSpaceSpeaker => ScheduledSpaceSpeakerCandidateGenerator.getCandidate
case CRT.MagicFanoutSportsEvent =>
MagicFanoutSportsEventCandidateGenerator.getCandidate(
_,
_,
config.basketballGameScoreStore,
config.baseballGameScoreStore,
config.cricketMatchScoreStore,
config.soccerMatchScoreStore,
config.nflGameScoreStore,
config.semanticCoreMegadataStore
)
case CRT.MagicFanoutProductLaunch =>
MagicFanoutProductLaunchCandidateGenerator.getCandidate
case CRT.NewCreator =>
MagicFanoutCreatorEventCandidateGenerator.getCandidate
case CRT.CreatorSubscriber =>
MagicFanoutCreatorEventCandidateGenerator.getCandidate
case _ =>
throw new UnsupportedCrtException(
"UnsupportedCrtException for SendHandler: " + frigateNotification.commonRecommendationType)
}
}
candidateGenerator(target, frigateNotification)
}
}

View File

@ -1,55 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.common.base.ScheduledSpaceSpeakerCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.util.Future
object ScheduledSpaceSpeakerCandidateGenerator extends CandidateGenerator {
override def getCandidate(
targetUser: Target,
notification: FrigateNotification
): Future[RawCandidate] = {
/**
* frigateNotification recommendation type should be [[CommonRecommendationType.ScheduledSpaceSpeaker]]
*
**/
require(
notification.commonRecommendationType == CommonRecommendationType.ScheduledSpaceSpeaker,
"ScheduledSpaceSpeaker: unexpected CRT " + notification.commonRecommendationType
)
val spaceNotification = notification.spaceNotification.getOrElse(
throw new IllegalStateException("ScheduledSpaceSpeaker notification object not defined"))
require(
spaceNotification.hostUserId.isDefined,
"ScheduledSpaceSpeaker notification - hostUserId not defined"
)
val spaceHostId = spaceNotification.hostUserId
require(
spaceNotification.scheduledStartTime.isDefined,
"ScheduledSpaceSpeaker notification - scheduledStartTime not defined"
)
val scheduledStartTime = spaceNotification.scheduledStartTime.get
val candidate = new RawCandidate with ScheduledSpaceSpeakerCandidate {
override val target: Target = targetUser
override val frigateNotification: FrigateNotification = notification
override val spaceId: String = spaceNotification.broadcastId
override val hostId: Option[Long] = spaceHostId
override val startTime: Long = scheduledStartTime
override val speakerIds: Option[Seq[Long]] = spaceNotification.speakers
override val listenerIds: Option[Seq[Long]] = spaceNotification.listeners
}
Future.value(candidate)
}
}

View File

@ -1,55 +0,0 @@
package com.twitter.frigate.pushservice.send_handler.generator
import com.twitter.frigate.common.base.ScheduledSpaceSubscriberCandidate
import com.twitter.frigate.pushservice.model.PushTypes.RawCandidate
import com.twitter.frigate.pushservice.model.PushTypes.Target
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.util.Future
object ScheduledSpaceSubscriberCandidateGenerator extends CandidateGenerator {
override def getCandidate(
targetUser: Target,
notification: FrigateNotification
): Future[RawCandidate] = {
/**
* frigateNotification recommendation type should be [[CommonRecommendationType.ScheduledSpaceSubscriber]]
*
**/
require(
notification.commonRecommendationType == CommonRecommendationType.ScheduledSpaceSubscriber,
"ScheduledSpaceSubscriber: unexpected CRT " + notification.commonRecommendationType
)
val spaceNotification = notification.spaceNotification.getOrElse(
throw new IllegalStateException("ScheduledSpaceSubscriber notification object not defined"))
require(
spaceNotification.hostUserId.isDefined,
"ScheduledSpaceSubscriber notification - hostUserId not defined"
)
val spaceHostId = spaceNotification.hostUserId
require(
spaceNotification.scheduledStartTime.isDefined,
"ScheduledSpaceSubscriber notification - scheduledStartTime not defined"
)
val scheduledStartTime = spaceNotification.scheduledStartTime.get
val candidate = new RawCandidate with ScheduledSpaceSubscriberCandidate {
override val target: Target = targetUser
override val frigateNotification: FrigateNotification = notification
override val spaceId: String = spaceNotification.broadcastId
override val hostId: Option[Long] = spaceHostId
override val startTime: Long = scheduledStartTime
override val speakerIds: Option[Seq[Long]] = spaceNotification.speakers
override val listenerIds: Option[Seq[Long]] = spaceNotification.listeners
}
Future.value(candidate)
}
}

View File

@ -1,17 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.content_mixer.thriftscala.ContentMixer
import com.twitter.content_mixer.thriftscala.ContentMixerRequest
import com.twitter.content_mixer.thriftscala.ContentMixerResponse
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
case class ContentMixerStore(contentMixer: ContentMixer.MethodPerEndpoint)
extends ReadableStore[ContentMixerRequest, ContentMixerResponse] {
override def get(request: ContentMixerRequest): Future[Option[ContentMixerResponse]] = {
contentMixer.getCandidates(request).map { response =>
Some(response)
}
}
}

View File

@ -1,15 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.copyselectionservice.thriftscala._
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
class CopySelectionServiceStore(copySelectionServiceClient: CopySelectionService.FinagledClient)
extends ReadableStore[CopySelectionRequestV1, Copy] {
override def get(k: CopySelectionRequestV1): Future[Option[Copy]] =
copySelectionServiceClient.getSelectedCopy(CopySelectionRequest.V1(k)).map {
case CopySelectionResponse.V1(response) =>
Some(response.selectedCopy)
case _ => throw CopyServiceException(CopyServiceErrorCode.VersionNotFound)
}
}

View File

@ -1,58 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.cr_mixer.thriftscala.CrMixer
import com.twitter.cr_mixer.thriftscala.CrMixerTweetRequest
import com.twitter.cr_mixer.thriftscala.CrMixerTweetResponse
import com.twitter.cr_mixer.thriftscala.FrsTweetRequest
import com.twitter.cr_mixer.thriftscala.FrsTweetResponse
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.Stat
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.util.Future
/**
* Store to get content recs from content recommender.
*/
case class CrMixerTweetStore(
crMixer: CrMixer.MethodPerEndpoint
)(
implicit statsReceiver: StatsReceiver = NullStatsReceiver) {
private val requestsCounter = statsReceiver.counter("requests")
private val successCounter = statsReceiver.counter("success")
private val failuresCounter = statsReceiver.counter("failures")
private val nonEmptyCounter = statsReceiver.counter("non_empty")
private val emptyCounter = statsReceiver.counter("empty")
private val failuresScope = statsReceiver.scope("failures")
private val latencyStat = statsReceiver.stat("latency")
private def updateStats[T](f: => Future[Option[T]]): Future[Option[T]] = {
requestsCounter.incr()
Stat
.timeFuture(latencyStat)(f)
.onSuccess { r =>
if (r.isDefined) nonEmptyCounter.incr() else emptyCounter.incr()
successCounter.incr()
}
.onFailure { e =>
{
failuresCounter.incr()
failuresScope.counter(e.getClass.getName).incr()
}
}
}
def getTweetRecommendations(
request: CrMixerTweetRequest
): Future[Option[CrMixerTweetResponse]] = {
updateStats(crMixer.getTweetRecommendations(request).map { response =>
Some(response)
})
}
def getFRSTweetCandidates(request: FrsTweetRequest): Future[Option[FrsTweetResponse]] = {
updateStats(crMixer.getFrsBasedTweetRecommendations(request).map { response =>
Some(response)
})
}
}

View File

@ -1,28 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.explore_ranker.thriftscala.ExploreRanker
import com.twitter.explore_ranker.thriftscala.ExploreRankerResponse
import com.twitter.explore_ranker.thriftscala.ExploreRankerRequest
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
/** A Store for Video Tweet Recommendations from Explore
*
* @param exploreRankerService
*/
case class ExploreRankerStore(exploreRankerService: ExploreRanker.MethodPerEndpoint)
extends ReadableStore[ExploreRankerRequest, ExploreRankerResponse] {
/** Method to get video recommendations
*
* @param request explore ranker request object
* @return
*/
override def get(
request: ExploreRankerRequest
): Future[Option[ExploreRankerResponse]] = {
exploreRankerService.getRankedResults(request).map { response =>
Some(response)
}
}
}

View File

@ -1,46 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.follow_recommendations.thriftscala.FollowRecommendationsThriftService
import com.twitter.follow_recommendations.thriftscala.Recommendation
import com.twitter.follow_recommendations.thriftscala.RecommendationRequest
import com.twitter.follow_recommendations.thriftscala.RecommendationResponse
import com.twitter.follow_recommendations.thriftscala.UserRecommendation
import com.twitter.inject.Logging
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
case class FollowRecommendationsStore(
frsClient: FollowRecommendationsThriftService.MethodPerEndpoint,
statsReceiver: StatsReceiver)
extends ReadableStore[RecommendationRequest, RecommendationResponse]
with Logging {
private val scopedStats = statsReceiver.scope(getClass.getSimpleName)
private val requests = scopedStats.counter("requests")
private val valid = scopedStats.counter("valid")
private val invalid = scopedStats.counter("invalid")
private val numTotalResults = scopedStats.stat("total_results")
private val numValidResults = scopedStats.stat("valid_results")
override def get(request: RecommendationRequest): Future[Option[RecommendationResponse]] = {
requests.incr()
frsClient.getRecommendations(request).map { response =>
numTotalResults.add(response.recommendations.size)
val validRecs = response.recommendations.filter {
case Recommendation.User(_: UserRecommendation) =>
valid.incr()
true
case _ =>
invalid.incr()
false
}
numValidResults.add(validRecs.size)
Some(
RecommendationResponse(
recommendations = validRecs
))
}
}
}

View File

@ -1,190 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.finagle.stats.BroadcastStatsReceiver
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.logger.MRLogger
import com.twitter.frigate.common.store
import com.twitter.frigate.common.store.Fail
import com.twitter.frigate.common.store.IbisRequestInfo
import com.twitter.frigate.common.store.IbisResponse
import com.twitter.frigate.common.store.Sent
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
import com.twitter.frigate.thriftscala.CommonRecommendationType
import com.twitter.ibis2.service.thriftscala.Flags
import com.twitter.ibis2.service.thriftscala.FlowControl
import com.twitter.ibis2.service.thriftscala.Ibis2Request
import com.twitter.ibis2.service.thriftscala.Ibis2Response
import com.twitter.ibis2.service.thriftscala.Ibis2ResponseStatus
import com.twitter.ibis2.service.thriftscala.Ibis2Service
import com.twitter.ibis2.service.thriftscala.NotificationNotSentCode
import com.twitter.ibis2.service.thriftscala.TargetFanoutResult.NotSentReason
import com.twitter.util.Future
trait Ibis2Store extends store.Ibis2Store {
def send(ibis2Request: Ibis2Request, candidate: PushCandidate): Future[IbisResponse]
}
case class PushIbis2Store(
ibisClient: Ibis2Service.MethodPerEndpoint
)(
implicit val statsReceiver: StatsReceiver = NullStatsReceiver)
extends Ibis2Store {
private val log = MRLogger(this.getClass.getSimpleName)
private val stats = statsReceiver.scope("ibis_v2_store")
private val statsByCrt = stats.scope("byCrt")
private val requestsByCrt = statsByCrt.scope("requests")
private val failuresByCrt = statsByCrt.scope("failures")
private val successByCrt = statsByCrt.scope("success")
private val statsByIbisModel = stats.scope("byIbisModel")
private val requestsByIbisModel = statsByIbisModel.scope("requests")
private val failuresByIbisModel = statsByIbisModel.scope("failures")
private val successByIbisModel = statsByIbisModel.scope("success")
private[this] def ibisSend(
ibis2Request: Ibis2Request,
commonRecommendationType: CommonRecommendationType
): Future[IbisResponse] = {
val ibisModel = ibis2Request.modelName
val bStats = if (ibis2Request.flags.getOrElse(Flags()).darkWrite.contains(true)) {
BroadcastStatsReceiver(
Seq(
stats,
stats.scope("dark_write")
)
)
} else BroadcastStatsReceiver(Seq(stats))
bStats.counter("requests").incr()
requestsByCrt.counter(commonRecommendationType.name).incr()
requestsByIbisModel.counter(ibisModel).incr()
retry(ibisClient, ibis2Request, 3, bStats)
.map { response =>
bStats.counter(response.status.status.name).incr()
successByCrt.counter(response.status.status.name, commonRecommendationType.name).incr()
successByIbisModel.counter(response.status.status.name, ibisModel).incr()
response.status.status match {
case Ibis2ResponseStatus.SuccessWithDeliveries |
Ibis2ResponseStatus.SuccessNoDeliveries =>
IbisResponse(Sent, Some(response))
case _ =>
IbisResponse(Fail, Some(response))
}
}
.onFailure { ex =>
bStats.counter("failures").incr()
val exceptionName = ex.getClass.getCanonicalName
bStats.scope("failures").counter(exceptionName).incr()
failuresByCrt.counter(exceptionName, commonRecommendationType.name).incr()
failuresByIbisModel.counter(exceptionName, ibisModel).incr()
}
}
private def getNotifNotSentReason(
ibis2Response: Ibis2Response
): Option[NotificationNotSentCode] = {
ibis2Response.status.fanoutResults match {
case Some(fanoutResult) =>
fanoutResult.pushResult.flatMap { pushResult =>
pushResult.results.headOption match {
case Some(NotSentReason(notSentInfo)) => Some(notSentInfo.notSentCode)
case _ => None
}
}
case _ => None
}
}
def send(ibis2Request: Ibis2Request, candidate: PushCandidate): Future[IbisResponse] = {
val requestWithIID = if (ibis2Request.flowControl.exists(_.externalIid.isDefined)) {
ibis2Request
} else {
ibis2Request.copy(
flowControl = Some(
ibis2Request.flowControl
.getOrElse(FlowControl())
.copy(externalIid = Some(candidate.impressionId))
)
)
}
val commonRecommendationType = candidate.frigateNotification.commonRecommendationType
ibisSend(requestWithIID, commonRecommendationType)
.onSuccess { response =>
response.ibis2Response.foreach { ibis2Response =>
getNotifNotSentReason(ibis2Response).foreach { notifNotSentCode =>
stats.scope(ibis2Response.status.status.name).counter(s"$notifNotSentCode").incr()
}
if (ibis2Response.status.status != Ibis2ResponseStatus.SuccessWithDeliveries) {
log.warning(
s"Request dropped on ibis for ${ibis2Request.recipientSelector.recipientId}: $ibis2Response")
}
}
}
.onFailure { ex =>
log.warning(
s"Ibis Request failure: ${ex.getClass.getCanonicalName} \n For IbisRequest: $ibis2Request")
log.error(ex, ex.getMessage)
}
}
// retry request when Ibis2ResponseStatus is PreFanoutError
def retry(
ibisClient: Ibis2Service.MethodPerEndpoint,
request: Ibis2Request,
retryCount: Int,
bStats: StatsReceiver
): Future[Ibis2Response] = {
ibisClient.sendNotification(request).flatMap { response =>
response.status.status match {
case Ibis2ResponseStatus.PreFanoutError if retryCount > 0 =>
bStats.scope("requests").counter("retry").incr()
bStats.counter(response.status.status.name).incr()
retry(ibisClient, request, retryCount - 1, bStats)
case _ =>
Future.value(response)
}
}
}
override def send(
ibis2Request: Ibis2Request,
requestInfo: IbisRequestInfo
): Future[IbisResponse] = {
ibisSend(ibis2Request, requestInfo.commonRecommendationType)
}
}
case class StagingIbis2Store(remoteIbis2Store: PushIbis2Store) extends Ibis2Store {
final def addDarkWriteFlagIbis2Request(
isTeamMember: Boolean,
ibis2Request: Ibis2Request
): Ibis2Request = {
val flags =
ibis2Request.flags.getOrElse(Flags())
val darkWrite: Boolean = !isTeamMember || flags.darkWrite.getOrElse(false)
ibis2Request.copy(flags = Some(flags.copy(darkWrite = Some(darkWrite))))
}
override def send(ibis2Request: Ibis2Request, candidate: PushCandidate): Future[IbisResponse] = {
candidate.target.isTeamMember.flatMap { isTeamMember =>
val ibis2Req = addDarkWriteFlagIbis2Request(isTeamMember, ibis2Request)
remoteIbis2Store.send(ibis2Req, candidate)
}
}
override def send(
ibis2Request: Ibis2Request,
requestInfo: IbisRequestInfo
): Future[IbisResponse] = {
requestInfo.isTeamMember.flatMap { isTeamMember =>
val ibis2Req = addDarkWriteFlagIbis2Request(isTeamMember, ibis2Request)
remoteIbis2Store.send(ibis2Req, requestInfo)
}
}
}

View File

@ -1,16 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.interests_discovery.thriftscala.InterestsDiscoveryService
import com.twitter.interests_discovery.thriftscala.RecommendedListsRequest
import com.twitter.interests_discovery.thriftscala.RecommendedListsResponse
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
case class InterestDiscoveryStore(
client: InterestsDiscoveryService.MethodPerEndpoint)
extends ReadableStore[RecommendedListsRequest, RecommendedListsResponse] {
override def get(request: RecommendedListsRequest): Future[Option[RecommendedListsResponse]] = {
client.getListRecos(request).map(Some(_))
}
}

View File

@ -1,156 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.conversions.DurationOps._
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.frigate.common.candidate.TargetDecider
import com.twitter.frigate.common.history.History
import com.twitter.frigate.common.history.HistoryStoreKeyContext
import com.twitter.frigate.common.history.PushServiceHistoryStore
import com.twitter.frigate.data_pipeline.thriftscala._
import com.twitter.frigate.thriftscala.FrigateNotification
import com.twitter.hermit.store.labeled_push_recs.LabeledPushRecsJoinedWithNotificationHistoryStore
import com.twitter.logging.Logger
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Future
import com.twitter.util.Time
case class LabeledPushRecsVerifyingStoreKey(
historyStoreKey: HistoryStoreKeyContext,
useHydratedDataset: Boolean,
verifyHydratedDatasetResults: Boolean) {
def userId: Long = historyStoreKey.targetUserId
}
case class LabeledPushRecsVerifyingStoreResponse(
userHistory: UserHistoryValue,
unequalNotificationsUnhydratedToHydrated: Option[
Map[(Time, FrigateNotification), FrigateNotification]
],
missingFromHydrated: Option[Map[Time, FrigateNotification]])
case class LabeledPushRecsVerifyingStore(
labeledPushRecsStore: ReadableStore[UserHistoryKey, UserHistoryValue],
historyStore: PushServiceHistoryStore
)(
implicit stats: StatsReceiver)
extends ReadableStore[LabeledPushRecsVerifyingStoreKey, LabeledPushRecsVerifyingStoreResponse] {
private def getByJoiningWithRealHistory(
key: HistoryStoreKeyContext
): Future[Option[UserHistoryValue]] = {
val historyFut = historyStore.get(key, Some(365.days))
val toJoinWithRealHistoryFut = labeledPushRecsStore.get(UserHistoryKey.UserId(key.targetUserId))
Future.join(historyFut, toJoinWithRealHistoryFut).map {
case (_, None) => None
case (History(realtimeHistoryMap), Some(uhValue)) =>
Some(
LabeledPushRecsJoinedWithNotificationHistoryStore
.joinLabeledPushRecsSentWithNotificationHistory(uhValue, realtimeHistoryMap, stats)
)
}
}
private def processUserHistoryValue(uhValue: UserHistoryValue): Map[Time, FrigateNotification] = {
uhValue.events
.getOrElse(Nil)
.collect {
case Event(
EventType.LabeledPushRecSend,
Some(tsMillis),
Some(EventUnion.LabeledPushRecSendEvent(lprs: LabeledPushRecSendEvent))
) if lprs.pushRecSendEvent.frigateNotification.isDefined =>
Time.fromMilliseconds(tsMillis) -> lprs.pushRecSendEvent.frigateNotification.get
}
.toMap
}
override def get(
key: LabeledPushRecsVerifyingStoreKey
): Future[Option[LabeledPushRecsVerifyingStoreResponse]] = {
val uhKey = UserHistoryKey.UserId(key.userId)
if (!key.useHydratedDataset) {
getByJoiningWithRealHistory(key.historyStoreKey).map { uhValueOpt =>
uhValueOpt.map { uhValue => LabeledPushRecsVerifyingStoreResponse(uhValue, None, None) }
}
} else {
labeledPushRecsStore.get(uhKey).flatMap { hydratedValueOpt: Option[UserHistoryValue] =>
if (!key.verifyHydratedDatasetResults) {
Future.value(hydratedValueOpt.map { uhValue =>
LabeledPushRecsVerifyingStoreResponse(uhValue, None, None)
})
} else {
getByJoiningWithRealHistory(key.historyStoreKey).map {
joinedWithRealHistoryOpt: Option[UserHistoryValue] =>
val joinedWithRealHistoryMap =
joinedWithRealHistoryOpt.map(processUserHistoryValue).getOrElse(Map.empty)
val hydratedMap = hydratedValueOpt.map(processUserHistoryValue).getOrElse(Map.empty)
val unequal = joinedWithRealHistoryMap.flatMap {
case (time, frigateNotif) =>
hydratedMap.get(time).collect {
case n if n != frigateNotif => ((time, frigateNotif), n)
}
}
val missing = joinedWithRealHistoryMap.filter {
case (time, frigateNotif) => !hydratedMap.contains(time)
}
hydratedValueOpt.map { hydratedValue =>
LabeledPushRecsVerifyingStoreResponse(hydratedValue, Some(unequal), Some(missing))
}
}
}
}
}
}
}
case class LabeledPushRecsStoreKey(target: TargetDecider, historyStoreKey: HistoryStoreKeyContext) {
def userId: Long = historyStoreKey.targetUserId
}
case class LabeledPushRecsDecideredStore(
verifyingStore: ReadableStore[
LabeledPushRecsVerifyingStoreKey,
LabeledPushRecsVerifyingStoreResponse
],
useHydratedLabeledSendsDatasetDeciderKey: String,
verifyHydratedLabeledSendsForHistoryDeciderKey: String
)(
implicit globalStats: StatsReceiver)
extends ReadableStore[LabeledPushRecsStoreKey, UserHistoryValue] {
private val log = Logger()
private val stats = globalStats.scope("LabeledPushRecsDecideredStore")
private val numComparisons = stats.counter("num_comparisons")
private val numMissingStat = stats.stat("num_missing")
private val numUnequalStat = stats.stat("num_unequal")
override def get(key: LabeledPushRecsStoreKey): Future[Option[UserHistoryValue]] = {
val useHydrated = key.target.isDeciderEnabled(
useHydratedLabeledSendsDatasetDeciderKey,
stats,
useRandomRecipient = true
)
val verifyHydrated = if (useHydrated) {
key.target.isDeciderEnabled(
verifyHydratedLabeledSendsForHistoryDeciderKey,
stats,
useRandomRecipient = true
)
} else false
val newKey = LabeledPushRecsVerifyingStoreKey(key.historyStoreKey, useHydrated, verifyHydrated)
verifyingStore.get(newKey).map {
case None => None
case Some(LabeledPushRecsVerifyingStoreResponse(uhValue, unequalOpt, missingOpt)) =>
(unequalOpt, missingOpt) match {
case (Some(unequal), Some(missing)) =>
numComparisons.incr()
numMissingStat.add(missing.size)
numUnequalStat.add(unequal.size)
case _ => //no-op
}
Some(uhValue)
}
}
}

View File

@ -1,26 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.livevideo.common.ids.EventId
import com.twitter.livevideo.timeline.client.v2.LiveVideoTimelineClient
import com.twitter.livevideo.timeline.domain.v2.Event
import com.twitter.livevideo.timeline.domain.v2.LookupContext
import com.twitter.stitch.storehaus.ReadableStoreOfStitch
import com.twitter.stitch.NotFound
import com.twitter.stitch.Stitch
import com.twitter.storehaus.ReadableStore
case class EventRequest(eventId: Long, lookupContext: LookupContext = LookupContext.default)
object LexServiceStore {
def apply(
liveVideoTimelineClient: LiveVideoTimelineClient
): ReadableStore[EventRequest, Event] = {
ReadableStoreOfStitch { eventRequest =>
liveVideoTimelineClient.getEvent(
EventId(eventRequest.eventId),
eventRequest.lookupContext) rescue {
case NotFound => Stitch.NotFound
}
}
}
}

View File

@ -1,45 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.hermit.store.common.ReadableWritableStore
import com.twitter.notificationservice.thriftscala.GenericNotificationOverrideKey
import com.twitter.stitch.Stitch
import com.twitter.storage.client.manhattan.bijections.Bijections.BinaryCompactScalaInjection
import com.twitter.storage.client.manhattan.bijections.Bijections.LongInjection
import com.twitter.storage.client.manhattan.bijections.Bijections.StringInjection
import com.twitter.storage.client.manhattan.kv.ManhattanKVEndpoint
import com.twitter.storage.client.manhattan.kv.impl.Component
import com.twitter.storage.client.manhattan.kv.impl.DescriptorP1L1
import com.twitter.storage.client.manhattan.kv.impl.KeyDescriptor
import com.twitter.storage.client.manhattan.kv.impl.ValueDescriptor
import com.twitter.util.Future
case class NTabHistoryStore(mhEndpoint: ManhattanKVEndpoint, dataset: String)
extends ReadableWritableStore[(Long, String), GenericNotificationOverrideKey] {
private val keyDesc: DescriptorP1L1.EmptyKey[Long, String] =
KeyDescriptor(Component(LongInjection), Component(StringInjection))
private val genericNotifKeyValDesc: ValueDescriptor.EmptyValue[GenericNotificationOverrideKey] =
ValueDescriptor[GenericNotificationOverrideKey](
BinaryCompactScalaInjection(GenericNotificationOverrideKey)
)
override def get(key: (Long, String)): Future[Option[GenericNotificationOverrideKey]] = {
val (userId, impressionId) = key
val mhKey = keyDesc.withDataset(dataset).withPkey(userId).withLkey(impressionId)
Stitch
.run(mhEndpoint.get(mhKey, genericNotifKeyValDesc))
.map { optionMhValue =>
optionMhValue.map(_.contents)
}
}
override def put(keyValue: ((Long, String), GenericNotificationOverrideKey)): Future[Unit] = {
val ((userId, impressionId), genericNotifOverrideKey) = keyValue
val mhKey = keyDesc.withDataset(dataset).withPkey(userId).withLkey(impressionId)
val mhVal = genericNotifKeyValDesc.withValue(genericNotifOverrideKey)
Stitch.run(mhEndpoint.insert(mhKey, mhVal))
}
}

View File

@ -1,73 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.conversions.DurationOps._
import com.twitter.finagle.stats.StatsReceiver
import com.twitter.onboarding.task.service.thriftscala.FatigueFlowEnrollment
import com.twitter.stitch.Stitch
import com.twitter.storage.client.manhattan.bijections.Bijections.BinaryScalaInjection
import com.twitter.storage.client.manhattan.bijections.Bijections.LongInjection
import com.twitter.storage.client.manhattan.bijections.Bijections.StringInjection
import com.twitter.storage.client.manhattan.kv.impl.Component
import com.twitter.storage.client.manhattan.kv.impl.KeyDescriptor
import com.twitter.storage.client.manhattan.kv.impl.ValueDescriptor
import com.twitter.storage.client.manhattan.kv.ManhattanKVClient
import com.twitter.storage.client.manhattan.kv.ManhattanKVClientMtlsParams
import com.twitter.storage.client.manhattan.kv.ManhattanKVEndpointBuilder
import com.twitter.storage.client.manhattan.kv.NoMtlsParams
import com.twitter.storehaus.ReadableStore
import com.twitter.storehaus_internal.manhattan.Omega
import com.twitter.util.Duration
import com.twitter.util.Future
import com.twitter.util.Time
case class OCFHistoryStoreKey(userId: Long, fatigueDuration: Duration, fatigueGroup: String)
class OCFPromptHistoryStore(
manhattanAppId: String,
dataset: String,
mtlsParams: ManhattanKVClientMtlsParams = NoMtlsParams
)(
implicit stats: StatsReceiver)
extends ReadableStore[OCFHistoryStoreKey, FatigueFlowEnrollment] {
import ManhattanInjections._
private val client = ManhattanKVClient(
appId = manhattanAppId,
dest = Omega.wilyName,
mtlsParams = mtlsParams,
label = "ocf_history_store"
)
private val endpoint = ManhattanKVEndpointBuilder(client, defaultMaxTimeout = 5.seconds)
.statsReceiver(stats.scope("ocf_history_store"))
.build()
private val limitResultsTo = 1
private val datasetKey = keyDesc.withDataset(dataset)
override def get(storeKey: OCFHistoryStoreKey): Future[Option[FatigueFlowEnrollment]] = {
val userId = storeKey.userId
val fatigueGroup = storeKey.fatigueGroup
val fatigueLength = storeKey.fatigueDuration.inMilliseconds
val currentTime = Time.now.inMilliseconds
val fullKey = datasetKey
.withPkey(userId)
.from(fatigueGroup)
.to(fatigueGroup, fatigueLength - currentTime)
Stitch
.run(endpoint.slice(fullKey, valDesc, limit = Some(limitResultsTo)))
.map { results =>
if (results.nonEmpty) {
val (_, mhValue) = results.head
Some(mhValue.contents)
} else None
}
}
}
object ManhattanInjections {
val keyDesc = KeyDescriptor(Component(LongInjection), Component(StringInjection, LongInjection))
val valDesc = ValueDescriptor(BinaryScalaInjection(FatigueFlowEnrollment))
}

View File

@ -1,81 +0,0 @@
package com.twitter.frigate.pushservice.store
import com.twitter.conversions.DurationOps._
import com.twitter.frigate.common.history.History
import com.twitter.frigate.common.store.RealTimeClientEventStore
import com.twitter.frigate.data_pipeline.common.HistoryJoin
import com.twitter.frigate.data_pipeline.thriftscala.Event
import com.twitter.frigate.data_pipeline.thriftscala.EventUnion
import com.twitter.frigate.data_pipeline.thriftscala.PushRecSendEvent
import com.twitter.frigate.data_pipeline.thriftscala.UserHistoryValue
import com.twitter.storehaus.ReadableStore
import com.twitter.util.Duration
import com.twitter.util.Future
import com.twitter.util.Time
case class OnlineUserHistoryKey(
userId: Long,
offlineUserHistory: Option[UserHistoryValue],
history: Option[History])
case class OnlineUserHistoryStore(
realTimeClientEventStore: RealTimeClientEventStore,
duration: Duration = 3.days)
extends ReadableStore[OnlineUserHistoryKey, UserHistoryValue] {
override def get(key: OnlineUserHistoryKey): Future[Option[UserHistoryValue]] = {
val now = Time.now
val pushRecSends = key.history
.getOrElse(History(Nil.toMap))
.sortedPushDmHistory
.filter(_._1 > now - (duration + 1.day))
.map {
case (time, frigateNotification) =>
val pushRecSendEvent = PushRecSendEvent(
frigateNotification = Some(frigateNotification),
impressionId = frigateNotification.impressionId
)
pushRecSendEvent -> time
}
realTimeClientEventStore
.get(key.userId, now - duration, now)
.map { attributedEventHistory =>
val attributedClientEvents = attributedEventHistory.sortedHistory.flatMap {
case (time, event) =>
event.eventUnion match {
case Some(eventUnion: EventUnion.AttributedPushRecClientEvent) =>
Some((eventUnion.attributedPushRecClientEvent, event.eventType, time))
case _ => None
}
}
val realtimeLabeledSends: Seq[Event] = HistoryJoin.getLabeledPushRecSends(
pushRecSends,
attributedClientEvents,
Seq(),
Seq(),
Seq(),
now
)
key.offlineUserHistory.map { offlineUserHistory =>
val combinedEvents = offlineUserHistory.events.map { offlineEvents =>
(offlineEvents ++ realtimeLabeledSends)
.map { event =>
event.timestampMillis -> event
}
.toMap
.values
.toSeq
.sortBy { event =>
-1 * event.timestampMillis.getOrElse(0L)
}
}
offlineUserHistory.copy(events = combinedEvents)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More