mirror of
https://github.com/twitter/the-algorithm.git
synced 2024-11-13 07:05:10 +01:00
[docx] split commit for file 2200
Signed-off-by: Ari Archer <ari.web.xyz@gmail.com>
This commit is contained in:
parent
2488f40edf
commit
b471ac86b4
@ -1,29 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-tweetypie",
|
||||
],
|
||||
exports = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-tweetypie",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,241 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.candidate.tweet_tweetypie
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.CandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLevel
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.tweetypie.{TweetyPie => TweetypieStitchClient}
|
||||
import com.twitter.tweetypie.thriftscala.TweetVisibilityPolicy
|
||||
import com.twitter.tweetypie.{thriftscala => TP}
|
||||
|
||||
// Candidate Features
|
||||
object IsCommunityTweetFeature extends Feature[TweetCandidate, Boolean]
|
||||
|
||||
// Tweetypie VF Features
|
||||
object HasTakedownFeature extends Feature[TweetCandidate, Boolean]
|
||||
object HasTakedownForLocaleFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsHydratedFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsNarrowcastFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsNsfwAdminFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsNsfwFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsNsfwUserFeature extends Feature[TweetCandidate, Boolean]
|
||||
object IsNullcastFeature extends Feature[TweetCandidate, Boolean]
|
||||
object QuotedTweetDroppedFeature extends Feature[TweetCandidate, Boolean]
|
||||
object QuotedTweetHasTakedownFeature extends Feature[TweetCandidate, Boolean]
|
||||
object QuotedTweetHasTakedownForLocaleFeature extends Feature[TweetCandidate, Boolean]
|
||||
object QuotedTweetIdFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object SourceTweetHasTakedownFeature extends Feature[TweetCandidate, Boolean]
|
||||
object SourceTweetHasTakedownForLocaleFeature extends Feature[TweetCandidate, Boolean]
|
||||
object TakedownCountryCodesFeature extends Feature[TweetCandidate, Set[String]]
|
||||
object IsReplyFeature extends Feature[TweetCandidate, Boolean]
|
||||
object InReplyToFeature extends Feature[TweetCandidate, Option[Long]]
|
||||
object IsRetweetFeature extends Feature[TweetCandidate, Boolean]
|
||||
|
||||
object TweetTweetypieCandidateFeatureHydrator {
|
||||
val CoreTweetFields: Set[TP.TweetInclude] = Set[TP.TweetInclude](
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.IdField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.CoreDataField.id)
|
||||
)
|
||||
|
||||
val NsfwLabelFields: Set[TP.TweetInclude] = Set[TP.TweetInclude](
|
||||
// Tweet fields containing NSFW related attributes, in addition to what exists in coreData.
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.NsfwHighRecallLabelField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.NsfwHighPrecisionLabelField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.NsfaHighRecallLabelField.id)
|
||||
)
|
||||
|
||||
val SafetyLabelFields: Set[TP.TweetInclude] = Set[TP.TweetInclude](
|
||||
// Tweet fields containing RTF labels for abuse and spam.
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.SpamLabelField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.AbusiveLabelField.id)
|
||||
)
|
||||
|
||||
val OrganicTweetTPHydrationFields: Set[TP.TweetInclude] = CoreTweetFields ++
|
||||
NsfwLabelFields ++
|
||||
SafetyLabelFields ++
|
||||
Set(
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.TakedownCountryCodesField.id),
|
||||
// QTs imply a TweetyPie -> SGS request dependency
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.QuotedTweetField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.EscherbirdEntityAnnotationsField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.CommunitiesField.id),
|
||||
// Field required for determining if a Tweet was created via News Camera.
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.ComposerSourceField.id)
|
||||
)
|
||||
|
||||
val InjectedTweetTPHydrationFields: Set[TP.TweetInclude] =
|
||||
OrganicTweetTPHydrationFields ++ Set(
|
||||
// Mentions imply a TweetyPie -> Gizmoduck request dependency
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.MentionsField.id),
|
||||
TP.TweetInclude.TweetFieldId(TP.Tweet.HashtagsField.id)
|
||||
)
|
||||
|
||||
val DefaultFeatureMap = FeatureMapBuilder()
|
||||
.add(IsNsfwAdminFeature, false)
|
||||
.add(IsNsfwUserFeature, false)
|
||||
.add(IsNsfwFeature, false)
|
||||
.add(IsNullcastFeature, false)
|
||||
.add(IsNarrowcastFeature, false)
|
||||
.add(HasTakedownFeature, false)
|
||||
.add(IsCommunityTweetFeature, false)
|
||||
.add(TakedownCountryCodesFeature, Set.empty: Set[String])
|
||||
.add(IsHydratedFeature, false)
|
||||
.add(HasTakedownForLocaleFeature, false)
|
||||
.add(QuotedTweetDroppedFeature, false)
|
||||
.add(SourceTweetHasTakedownFeature, false)
|
||||
.add(QuotedTweetHasTakedownFeature, false)
|
||||
.add(SourceTweetHasTakedownForLocaleFeature, false)
|
||||
.add(QuotedTweetHasTakedownForLocaleFeature, false)
|
||||
.add(IsReplyFeature, false)
|
||||
.add(InReplyToFeature, None)
|
||||
.add(IsRetweetFeature, false)
|
||||
.build()
|
||||
}
|
||||
|
||||
class TweetTweetypieCandidateFeatureHydrator(
|
||||
tweetypieStitchClient: TweetypieStitchClient,
|
||||
safetyLevelPredicate: PipelineQuery => SafetyLevel)
|
||||
extends CandidateFeatureHydrator[PipelineQuery, BaseTweetCandidate] {
|
||||
|
||||
import TweetTweetypieCandidateFeatureHydrator._
|
||||
|
||||
override val features: Set[Feature[_, _]] =
|
||||
Set(
|
||||
IsNsfwFeature,
|
||||
IsNsfwAdminFeature,
|
||||
IsNsfwUserFeature,
|
||||
IsNullcastFeature,
|
||||
IsNarrowcastFeature,
|
||||
HasTakedownFeature,
|
||||
IsCommunityTweetFeature,
|
||||
TakedownCountryCodesFeature,
|
||||
IsHydratedFeature,
|
||||
HasTakedownForLocaleFeature,
|
||||
QuotedTweetDroppedFeature,
|
||||
SourceTweetHasTakedownFeature,
|
||||
QuotedTweetHasTakedownFeature,
|
||||
SourceTweetHasTakedownForLocaleFeature,
|
||||
QuotedTweetHasTakedownForLocaleFeature,
|
||||
IsReplyFeature,
|
||||
InReplyToFeature,
|
||||
IsRetweetFeature
|
||||
)
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier =
|
||||
FeatureHydratorIdentifier("TweetTweetypie")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidate: BaseTweetCandidate,
|
||||
existingFeatures: FeatureMap
|
||||
): Stitch[FeatureMap] = {
|
||||
val countryCode = query.getCountryCode.getOrElse("")
|
||||
|
||||
tweetypieStitchClient
|
||||
.getTweetFields(
|
||||
tweetId = candidate.id,
|
||||
options = TP.GetTweetFieldsOptions(
|
||||
tweetIncludes = OrganicTweetTPHydrationFields,
|
||||
includeRetweetedTweet = true,
|
||||
includeQuotedTweet = true,
|
||||
visibilityPolicy = TweetVisibilityPolicy.UserVisible,
|
||||
safetyLevel = Some(safetyLevelPredicate(query))
|
||||
)
|
||||
).map {
|
||||
case TP.GetTweetFieldsResult(_, TP.TweetFieldsResultState.Found(found), quoteOpt, _) =>
|
||||
val coreData = found.tweet.coreData
|
||||
val isNsfwAdmin = coreData.exists(_.nsfwAdmin)
|
||||
val isNsfwUser = coreData.exists(_.nsfwUser)
|
||||
val hasTakedown = coreData.exists(_.hasTakedown)
|
||||
val isReply = coreData.exists(_.reply.nonEmpty)
|
||||
val ancestorId = coreData.flatMap(_.reply).flatMap(_.inReplyToStatusId)
|
||||
val isRetweet = coreData.exists(_.share.nonEmpty)
|
||||
val takedownCountryCodes =
|
||||
found.tweet.takedownCountryCodes.getOrElse(Seq.empty).map(_.toLowerCase).toSet
|
||||
|
||||
val quotedTweetDropped = quoteOpt.exists {
|
||||
case _: TP.TweetFieldsResultState.Filtered =>
|
||||
true
|
||||
case _: TP.TweetFieldsResultState.NotFound =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
val quotedTweetIsNsfw = quoteOpt.exists {
|
||||
case quoteTweet: TP.TweetFieldsResultState.Found =>
|
||||
quoteTweet.found.tweet.coreData.exists(data => data.nsfwAdmin || data.nsfwUser)
|
||||
case _ => false
|
||||
}
|
||||
val quotedTweetHasTakedown = quoteOpt.exists {
|
||||
case quoteTweet: TP.TweetFieldsResultState.Found =>
|
||||
quoteTweet.found.tweet.coreData.exists(_.hasTakedown)
|
||||
case _ => false
|
||||
}
|
||||
val quotedTweetTakedownCountryCodes = quoteOpt
|
||||
.collect {
|
||||
case quoteTweet: TP.TweetFieldsResultState.Found =>
|
||||
quoteTweet.found.tweet.takedownCountryCodes
|
||||
.getOrElse(Seq.empty).map(_.toLowerCase).toSet
|
||||
}.getOrElse(Set.empty[String])
|
||||
|
||||
val sourceTweetIsNsfw =
|
||||
found.retweetedTweet.exists(_.coreData.exists(data => data.nsfwAdmin || data.nsfwUser))
|
||||
val sourceTweetHasTakedown =
|
||||
found.retweetedTweet.exists(_.coreData.exists(_.hasTakedown))
|
||||
val sourceTweetTakedownCountryCodes = found.retweetedTweet
|
||||
.map { sourceTweet: TP.Tweet =>
|
||||
sourceTweet.takedownCountryCodes.getOrElse(Seq.empty).map(_.toLowerCase).toSet
|
||||
}.getOrElse(Set.empty)
|
||||
|
||||
FeatureMapBuilder()
|
||||
.add(IsNsfwAdminFeature, isNsfwAdmin)
|
||||
.add(IsNsfwUserFeature, isNsfwUser)
|
||||
.add(IsNsfwFeature, isNsfwAdmin || isNsfwUser || sourceTweetIsNsfw || quotedTweetIsNsfw)
|
||||
.add(IsNullcastFeature, coreData.exists(_.nullcast))
|
||||
.add(IsNarrowcastFeature, coreData.exists(_.narrowcast.nonEmpty))
|
||||
.add(HasTakedownFeature, hasTakedown)
|
||||
.add(
|
||||
HasTakedownForLocaleFeature,
|
||||
hasTakedownForLocale(hasTakedown, countryCode, takedownCountryCodes))
|
||||
.add(QuotedTweetDroppedFeature, quotedTweetDropped)
|
||||
.add(SourceTweetHasTakedownFeature, sourceTweetHasTakedown)
|
||||
.add(QuotedTweetHasTakedownFeature, quotedTweetHasTakedown)
|
||||
.add(
|
||||
SourceTweetHasTakedownForLocaleFeature,
|
||||
hasTakedownForLocale(
|
||||
sourceTweetHasTakedown,
|
||||
countryCode,
|
||||
sourceTweetTakedownCountryCodes))
|
||||
.add(
|
||||
QuotedTweetHasTakedownForLocaleFeature,
|
||||
hasTakedownForLocale(
|
||||
quotedTweetHasTakedown,
|
||||
countryCode,
|
||||
quotedTweetTakedownCountryCodes))
|
||||
.add(IsCommunityTweetFeature, found.tweet.communities.exists(_.communityIds.nonEmpty))
|
||||
.add(
|
||||
TakedownCountryCodesFeature,
|
||||
found.tweet.takedownCountryCodes.getOrElse(Seq.empty).map(_.toLowerCase).toSet)
|
||||
.add(IsHydratedFeature, true)
|
||||
.add(IsReplyFeature, isReply)
|
||||
.add(InReplyToFeature, ancestorId)
|
||||
.add(IsRetweetFeature, isRetweet)
|
||||
.build()
|
||||
|
||||
// If no tweet result found, return default features
|
||||
case _ =>
|
||||
DefaultFeatureMap
|
||||
}
|
||||
}
|
||||
|
||||
private def hasTakedownForLocale(
|
||||
hasTakedown: Boolean,
|
||||
countryCode: String,
|
||||
takedownCountryCodes: Set[String]
|
||||
) = hasTakedown && takedownCountryCodes.contains(countryCode)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-tweetypie",
|
||||
"util/util-slf4j-api",
|
||||
],
|
||||
exports = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/spam/rtf:safety-result-scala",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-tweetypie",
|
||||
"util/util-slf4j-api",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,98 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.candidate.tweet_visibility_reason
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.FeatureWithDefaultOnFailure
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.BulkCandidateFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.spam.rtf.{thriftscala => SPAM}
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.tweetypie.{TweetyPie => TweetypieStitchClient}
|
||||
import com.twitter.tweetypie.{thriftscala => TP}
|
||||
import com.twitter.util.Return
|
||||
import com.twitter.util.Throw
|
||||
import com.twitter.util.Try
|
||||
import com.twitter.util.logging.Logging
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
object VisibilityReason
|
||||
extends FeatureWithDefaultOnFailure[TweetCandidate, Option[SPAM.FilteredReason]] {
|
||||
override val defaultValue = None
|
||||
}
|
||||
|
||||
/**
|
||||
* A [[BulkCandidateFeatureHydrator]] that hydrates TweetCandidates with VisibilityReason features
|
||||
* by [[SPAM.SafetyLevel]] when present. The [[VisibilityReason]] feature represents a VisibilityFiltering
|
||||
* [[SPAM.FilteredReason]], which contains safety filtering verdict information including action (e.g.
|
||||
* Drop, Avoid) and reason (e.g. Misinformation, Abuse). This feature can inform downstream services'
|
||||
* handling and presentation of Tweets (e.g. ad avoidance).
|
||||
*
|
||||
* @param tweetypieStitchClient used to retrieve Tweet fields for BaseTweetCandidates
|
||||
* @param safetyLevel specifies VisibilityFiltering SafetyLabel
|
||||
*/
|
||||
|
||||
@Singleton
|
||||
case class TweetVisibilityReasonBulkCandidateFeatureHydrator @Inject() (
|
||||
tweetypieStitchClient: TweetypieStitchClient,
|
||||
safetyLevel: SPAM.SafetyLevel)
|
||||
extends BulkCandidateFeatureHydrator[PipelineQuery, BaseTweetCandidate]
|
||||
with Logging {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"TweetVisibilityReason")
|
||||
|
||||
override def features: Set[Feature[_, _]] = Set(VisibilityReason)
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[BaseTweetCandidate]]
|
||||
): Stitch[Seq[FeatureMap]] = {
|
||||
Stitch
|
||||
.traverse(candidates.map(_.candidate.id)) { tweetId =>
|
||||
tweetypieStitchClient
|
||||
.getTweetFields(
|
||||
tweetId = tweetId,
|
||||
options = TP.GetTweetFieldsOptions(
|
||||
forUserId = query.getOptionalUserId,
|
||||
tweetIncludes = Set.empty,
|
||||
doNotCache = true,
|
||||
visibilityPolicy = TP.TweetVisibilityPolicy.UserVisible,
|
||||
safetyLevel = Some(safetyLevel)
|
||||
)
|
||||
).liftToTry
|
||||
}.map { getTweetFieldsResults: Seq[Try[TP.GetTweetFieldsResult]] =>
|
||||
val tweetFields: Seq[Try[TP.TweetFieldsResultFound]] = getTweetFieldsResults.map {
|
||||
case Return(TP.GetTweetFieldsResult(_, TP.TweetFieldsResultState.Found(found), _, _)) =>
|
||||
Return(found)
|
||||
case Return(TP.GetTweetFieldsResult(_, resultState, _, _)) =>
|
||||
Throw(
|
||||
VisibilityReasonFeatureHydrationFailure(
|
||||
s"Unexpected tweet result state: ${resultState}"))
|
||||
case Throw(e) =>
|
||||
Throw(e)
|
||||
}
|
||||
|
||||
tweetFields.map { tweetFieldTry =>
|
||||
val tweetFilteredReason = tweetFieldTry.map { tweetField =>
|
||||
tweetField.suppressReason match {
|
||||
case Some(suppressReason) => Some(suppressReason)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
FeatureMapBuilder()
|
||||
.add(VisibilityReason, tweetFilteredReason)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class VisibilityReasonFeatureHydrationFailure(message: String)
|
||||
extends Exception(s"VisibilityReasonFeatureHydrationFailure($message)")
|
Binary file not shown.
@ -1,97 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.async
|
||||
|
||||
import com.twitter.ml.featurestore.lib.EntityId
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featurestorev1.BaseFeatureStoreV1QueryFeature
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.AsyncHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1DynamicClientBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.PipelineStepIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[AsyncQueryFeatureHydrator]] that hydrated asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]
|
||||
*
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run asynchronously
|
||||
* @tparam Query The domain model for the query or request
|
||||
*/
|
||||
case class AsyncQueryFeatureHydrator[-Query <: PipelineQuery] private[async] (
|
||||
override val hydrateBefore: PipelineStepIdentifier,
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query])
|
||||
extends QueryFeatureHydrator[Query]
|
||||
with AsyncHydrator {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"Async" + queryFeatureHydrator.identifier.name)
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
override val features: Set[Feature[_, _]] = queryFeatureHydrator.features
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [[FeatureStoreV1QueryFeatureHydrator]] with [[AsyncHydrator]] that hydrated asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]. We need a standalone class for feature store,
|
||||
* different from the above as FStore hydrators are exempt from validations at run time.
|
||||
*
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run asynchronously
|
||||
* @tparam Query The domain model for the query or request
|
||||
*/
|
||||
case class AsyncFeatureStoreV1QueryFeatureHydrator[Query <: PipelineQuery] private[async] (
|
||||
override val hydrateBefore: PipelineStepIdentifier,
|
||||
featureStoreV1QueryFeatureHydrator: FeatureStoreV1QueryFeatureHydrator[Query])
|
||||
extends FeatureStoreV1QueryFeatureHydrator[
|
||||
Query
|
||||
]
|
||||
with AsyncHydrator {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"Async" + featureStoreV1QueryFeatureHydrator.identifier.name)
|
||||
override val alerts: Seq[Alert] = featureStoreV1QueryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[BaseFeatureStoreV1QueryFeature[Query, _ <: EntityId, _]] =
|
||||
featureStoreV1QueryFeatureHydrator.features
|
||||
|
||||
override val clientBuilder: FeatureStoreV1DynamicClientBuilder =
|
||||
featureStoreV1QueryFeatureHydrator.clientBuilder
|
||||
}
|
||||
|
||||
object AsyncQueryFeatureHydrator {
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[AsyncQueryFeatureHydrator]] that hydrated asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]
|
||||
*
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run asynchronously
|
||||
* @tparam Query The domain model for the query or request
|
||||
*/
|
||||
def apply[Query <: PipelineQuery](
|
||||
hydrateBefore: PipelineStepIdentifier,
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query]
|
||||
): AsyncQueryFeatureHydrator[Query] =
|
||||
new AsyncQueryFeatureHydrator(hydrateBefore, queryFeatureHydrator)
|
||||
|
||||
/**
|
||||
* A [[FeatureStoreV1QueryFeatureHydrator]] with [[AsyncHydrator]] that hydrated asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]. We need a standalone class for feature store,
|
||||
* different from the above as FStore hydrators are exempt from validations at run time.
|
||||
*
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run asynchronously
|
||||
* @tparam Query The domain model for the query or request
|
||||
*/
|
||||
def apply[Query <: PipelineQuery](
|
||||
hydrateBefore: PipelineStepIdentifier,
|
||||
featureStoreV1QueryFeatureHydrator: FeatureStoreV1QueryFeatureHydrator[Query]
|
||||
): AsyncFeatureStoreV1QueryFeatureHydrator[Query] =
|
||||
new AsyncFeatureStoreV1QueryFeatureHydrator(hydrateBefore, featureStoreV1QueryFeatureHydrator)
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator/featurestorev1",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator/featurestorev1",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
@ -1,19 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"cr-ml-ranker/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"cr-ml-ranker/thrift/src/main/thrift:thrift-scala",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,37 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.cr_ml_ranker
|
||||
|
||||
import com.twitter.cr_ml_ranker.{thriftscala => t}
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
object CrMlRankerCommonFeatures extends Feature[PipelineQuery, t.CommonFeatures]
|
||||
object CrMlRankerRankingConfig extends Feature[PipelineQuery, t.RankingConfig]
|
||||
|
||||
private[cr_ml_ranker] class CrMlRankerCommonQueryFeatureHydrator(
|
||||
crMlRanker: t.CrMLRanker.MethodPerEndpoint,
|
||||
rankingConfigSelector: RankingConfigBuilder)
|
||||
extends QueryFeatureHydrator[PipelineQuery] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier("CrMlRanker")
|
||||
|
||||
override val features: Set[Feature[_, _]] =
|
||||
Set(CrMlRankerCommonFeatures, CrMlRankerRankingConfig)
|
||||
|
||||
override def hydrate(query: PipelineQuery): Stitch[FeatureMap] = {
|
||||
val rankingConfig = rankingConfigSelector.apply(query)
|
||||
Stitch
|
||||
.callFuture(
|
||||
crMlRanker.getCommonFeatures(
|
||||
t.RankingRequestContext(query.getRequiredUserId, rankingConfig))).map { commonFeatures =>
|
||||
FeatureMapBuilder()
|
||||
.add(CrMlRankerRankingConfig, rankingConfig)
|
||||
.add(CrMlRankerCommonFeatures, commonFeatures)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,18 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.cr_ml_ranker
|
||||
|
||||
import com.twitter.cr_ml_ranker.{thriftscala => t}
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Builds a query hydrator that hydrates Common Features for the given Query from CR ML Ranker
|
||||
* to be later used to call CR ML Ranker for scoring using the desired [[RankingConfigBuilder]]
|
||||
* for building the ranking config.
|
||||
*/
|
||||
@Singleton
|
||||
class CrMlRankerCommonQueryFeatureHydratorBuilder @Inject() (
|
||||
crMlRanker: t.CrMLRanker.MethodPerEndpoint) {
|
||||
|
||||
def build(rankingConfigSelector: RankingConfigBuilder): CrMlRankerCommonQueryFeatureHydrator =
|
||||
new CrMlRankerCommonQueryFeatureHydrator(crMlRanker, rankingConfigSelector)
|
||||
}
|
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.cr_ml_ranker
|
||||
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.cr_ml_ranker.{thriftscala => t}
|
||||
|
||||
/**
|
||||
* Builder for constructing a ranking config from a query
|
||||
*/
|
||||
trait RankingConfigBuilder {
|
||||
def apply(query: PipelineQuery): t.RankingConfig
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"3rdparty/src/jvm/com/twitter/storehaus:core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/timelines/impression_store:thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"3rdparty/jvm/javax/inject:javax.inject",
|
||||
"3rdparty/src/jvm/com/twitter/storehaus:core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"src/thrift/com/twitter/timelines/impression_store:thrift-scala",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,57 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.impressed_tweets
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.FeatureWithDefaultOnFailure
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.storehaus.ReadableStore
|
||||
import com.twitter.timelines.impressionstore.thriftscala.ImpressionList
|
||||
import com.twitter.util.Future
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Query Feature to store ids of the tweets impressed by the user.
|
||||
*/
|
||||
case object ImpressedTweets extends FeatureWithDefaultOnFailure[PipelineQuery, Seq[Long]] {
|
||||
override val defaultValue: Seq[Long] = Seq.empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich the query with a list of tweet ids that the user has already seen.
|
||||
*/
|
||||
@Singleton
|
||||
case class ImpressedTweetsQueryFeatureHydrator @Inject() (
|
||||
tweetImpressionStore: ReadableStore[Long, ImpressionList])
|
||||
extends QueryFeatureHydrator[PipelineQuery] {
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier("TweetsToExclude")
|
||||
|
||||
override val features: Set[Feature[_, _]] = Set(ImpressedTweets)
|
||||
|
||||
override def hydrate(query: PipelineQuery): Stitch[FeatureMap] = {
|
||||
query.getOptionalUserId match {
|
||||
case Some(userId) =>
|
||||
val featureMapResult: Future[FeatureMap] = tweetImpressionStore
|
||||
.get(userId).map { impressionListOpt =>
|
||||
val tweetIdsOpt = for {
|
||||
impressionList <- impressionListOpt
|
||||
impressions <- impressionList.impressions
|
||||
} yield {
|
||||
impressions.map(_.tweetId)
|
||||
}
|
||||
val tweetIds = tweetIdsOpt.getOrElse(Seq.empty)
|
||||
FeatureMapBuilder().add(ImpressedTweets, tweetIds).build()
|
||||
}
|
||||
Stitch.callFuture(featureMapResult)
|
||||
// Non-logged-in users do not have userId, returns empty feature
|
||||
|
||||
case None =>
|
||||
val featureMapResult = FeatureMapBuilder().add(ImpressedTweets, Seq.empty).build()
|
||||
Stitch.value(featureMapResult)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,31 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.logged_in_only
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] to run only for logged in users
|
||||
*
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when query.isLoggedOut is false
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class LoggedInOnlyQueryFeatureHydrator[-Query <: PipelineQuery, Result <: UniversalNoun[Any]](
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query])
|
||||
extends QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query] {
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"LoggedInOnly" + queryFeatureHydrator.identifier.name)
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
override val features: Set[Feature[_, _]] = queryFeatureHydrator.features
|
||||
override def onlyIf(query: Query): Boolean =
|
||||
Conditionally.and(query, queryFeatureHydrator, !query.isLoggedOut)
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
Binary file not shown.
@ -1,48 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.AsyncHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.PipelineStepIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] based on a [[Param]] that hydrates asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]
|
||||
*
|
||||
* @param enabledParam the param to turn this [[QueryFeatureHydrator]] on and off
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when `enabledParam` is true
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class AsyncParamGatedQueryFeatureHydrator[
|
||||
-Query <: PipelineQuery,
|
||||
Result <: UniversalNoun[Any]
|
||||
](
|
||||
enabledParam: Param[Boolean],
|
||||
override val hydrateBefore: PipelineStepIdentifier,
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query])
|
||||
extends QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query]
|
||||
with AsyncHydrator {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"AsyncParamGated" + queryFeatureHydrator.identifier.name)
|
||||
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[Feature[_, _]] = queryFeatureHydrator.features
|
||||
|
||||
override def onlyIf(query: Query): Boolean =
|
||||
Conditionally.and(query, queryFeatureHydrator, query.params(enabledParam))
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,39 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] based on a [[Param]]
|
||||
*
|
||||
* @param enabledParam the param to turn this [[QueryFeatureHydrator]] on and off
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when `enabledParam` is true
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class ParamGatedQueryFeatureHydrator[-Query <: PipelineQuery, Result <: UniversalNoun[Any]](
|
||||
enabledParam: Param[Boolean],
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query])
|
||||
extends QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"ParamGated" + queryFeatureHydrator.identifier.name)
|
||||
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[Feature[_, _]] = queryFeatureHydrator.features
|
||||
|
||||
override def onlyIf(query: Query): Boolean =
|
||||
Conditionally.and(query, queryFeatureHydrator, query.params(enabledParam))
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
Binary file not shown.
@ -1,53 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated.featurestorev1
|
||||
|
||||
import com.twitter.ml.featurestore.lib.EntityId
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featurestorev1.BaseFeatureStoreV1QueryFeature
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.AsyncHydrator
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1DynamicClientBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.PipelineStepIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] based on a [[Param]] that hydrates asynchronously for features
|
||||
* to be before the step identified in [[hydrateBefore]]
|
||||
*
|
||||
* @param enabledParam the param to turn this [[QueryFeatureHydrator]] on and off
|
||||
* @param hydrateBefore the [[PipelineStepIdentifier]] step to make sure this feature is hydrated before.
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when `enabledParam` is true
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class AsyncParamGatedFeatureStoreV1QueryFeatureHydrator[
|
||||
Query <: PipelineQuery,
|
||||
Result <: UniversalNoun[Any]
|
||||
](
|
||||
enabledParam: Param[Boolean],
|
||||
override val hydrateBefore: PipelineStepIdentifier,
|
||||
queryFeatureHydrator: FeatureStoreV1QueryFeatureHydrator[Query])
|
||||
extends FeatureStoreV1QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query]
|
||||
with AsyncHydrator {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"AsyncParamGated" + queryFeatureHydrator.identifier.name)
|
||||
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[BaseFeatureStoreV1QueryFeature[Query, _ <: EntityId, _]] =
|
||||
queryFeatureHydrator.features
|
||||
|
||||
override val clientBuilder: FeatureStoreV1DynamicClientBuilder =
|
||||
queryFeatureHydrator.clientBuilder
|
||||
override def onlyIf(query: Query): Boolean =
|
||||
Conditionally.and(query, queryFeatureHydrator, query.params(enabledParam))
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator/featurestorev1",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
exports = [
|
||||
"configapi/configapi-core",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/feature",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common/alert",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/feature_hydrator/featurestorev1",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"stitch/stitch-core",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,47 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.param_gated.featurestorev1
|
||||
|
||||
import com.twitter.ml.featurestore.lib.EntityId
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.feature.featurestorev1.BaseFeatureStoreV1QueryFeature
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1DynamicClientBuilder
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.featurestorev1.FeatureStoreV1QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] based on a [[Param]]
|
||||
*
|
||||
* @param enabledParam the param to turn this [[QueryFeatureHydrator]] on and off
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when `enabledParam` is true
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class ParamGatedFeatureStoreV1QueryFeatureHydrator[
|
||||
Query <: PipelineQuery,
|
||||
Result <: UniversalNoun[Any]
|
||||
](
|
||||
enabledParam: Param[Boolean],
|
||||
queryFeatureHydrator: FeatureStoreV1QueryFeatureHydrator[Query])
|
||||
extends FeatureStoreV1QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query] {
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
"ParamGated" + queryFeatureHydrator.identifier.name)
|
||||
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[BaseFeatureStoreV1QueryFeature[Query, _ <: EntityId, _]] =
|
||||
queryFeatureHydrator.features
|
||||
|
||||
override val clientBuilder: FeatureStoreV1DynamicClientBuilder =
|
||||
queryFeatureHydrator.clientBuilder
|
||||
override def onlyIf(query: Query): Boolean =
|
||||
Conditionally.and(query, queryFeatureHydrator, query.params(enabledParam))
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,54 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.feature_hydrator.query.qualityfactor_gated
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.FeatureMap
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.feature_hydrator.QueryFeatureHydrator
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ComponentIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FeatureHydratorIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.quality_factor.HasQualityFactorStatus
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
object QualityFactorGatedQueryFeatureHydrator {
|
||||
val IdentifierPrefix = "QfGated"
|
||||
}
|
||||
|
||||
/**
|
||||
* A [[QueryFeatureHydrator]] with [[Conditionally]] based on a qualityFactor threshold.
|
||||
* @param pipelineIdentifier identifier of the pipeline that associated with observed quality factor
|
||||
* @param qualityFactorInclusiveThreshold the threshold of the quality factor that results in the hydrator being turned off
|
||||
* @param queryFeatureHydrator the underlying [[QueryFeatureHydrator]] to run when quality factor value
|
||||
* is above the given inclusive threshold
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Result The type of the candidates
|
||||
*/
|
||||
case class QualityFactorGatedQueryFeatureHydrator[
|
||||
-Query <: PipelineQuery with HasQualityFactorStatus,
|
||||
Result <: UniversalNoun[Any]
|
||||
](
|
||||
pipelineIdentifier: ComponentIdentifier,
|
||||
qualityFactorInclusiveThreshold: Param[Double],
|
||||
queryFeatureHydrator: QueryFeatureHydrator[Query])
|
||||
extends QueryFeatureHydrator[Query]
|
||||
with Conditionally[Query] {
|
||||
import QualityFactorGatedQueryFeatureHydrator._
|
||||
|
||||
override val identifier: FeatureHydratorIdentifier = FeatureHydratorIdentifier(
|
||||
IdentifierPrefix + queryFeatureHydrator.identifier.name)
|
||||
|
||||
override val alerts: Seq[Alert] = queryFeatureHydrator.alerts
|
||||
|
||||
override val features: Set[Feature[_, _]] = queryFeatureHydrator.features
|
||||
|
||||
override def onlyIf(query: Query): Boolean = Conditionally.and(
|
||||
query,
|
||||
queryFeatureHydrator,
|
||||
query.getQualityFactorCurrentValue(pipelineIdentifier) >= query.params(
|
||||
qualityFactorInclusiveThreshold))
|
||||
|
||||
override def hydrate(query: Query): Stitch[FeatureMap] = queryFeatureHydrator.hydrate(query)
|
||||
}
|
Binary file not shown.
@ -1,40 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.search.common.util.bloomfilter.AdaptiveLongIntBloomFilter
|
||||
|
||||
trait GetAdaptiveLongIntBloomFilter[Query <: PipelineQuery] {
|
||||
def apply(query: Query): Option[AdaptiveLongIntBloomFilter]
|
||||
}
|
||||
|
||||
case class AdaptiveLongIntBloomFilterDedupFilter[
|
||||
Query <: PipelineQuery,
|
||||
Candidate <: UniversalNoun[Long]
|
||||
](
|
||||
getBloomFilter: GetAdaptiveLongIntBloomFilter[Query])
|
||||
extends Filter[Query, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier(
|
||||
"AdaptiveLongIntBloomFilterDedupFilter")
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val filterResult = getBloomFilter(query)
|
||||
.map { bloomFilter =>
|
||||
val (kept, removed) =
|
||||
candidates.map(_.candidate).partition(candidate => !bloomFilter.contains(candidate.id))
|
||||
FilterResult(kept, removed)
|
||||
}.getOrElse(FilterResult(candidates.map(_.candidate), Seq.empty))
|
||||
|
||||
Stitch.value(filterResult)
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
platform = "java8",
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"finatra/inject/inject-core/src/main/scala",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/candidate/tweet_tweetypie",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/cursor",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/model/common/identifier",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/pipeline",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/java/com/twitter/search/common/util/bloomfilter",
|
||||
"src/thrift/com/twitter/tweetypie:service-scala",
|
||||
"src/thrift/com/twitter/tweetypie:tweet-scala",
|
||||
"stitch/stitch-core",
|
||||
"stitch/stitch-tweetypie",
|
||||
"stitch/stitch-tweetypie/src/main/scala",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/configapi",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
"snowflake/src/main/scala/com/twitter/snowflake/id",
|
||||
"src/java/com/twitter/search/common/util/bloomfilter",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,28 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.model.marshalling.request.HasExcludedIds
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
case class ExcludedIdsFilter[
|
||||
Query <: PipelineQuery with HasExcludedIds,
|
||||
Candidate <: UniversalNoun[Long]
|
||||
]() extends Filter[Query, Candidate] {
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("ExcludedIds")
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (kept, removed) =
|
||||
candidates.map(_.candidate).partition(candidate => !query.excludedIds.contains(candidate.id))
|
||||
|
||||
val filterResult = FilterResult(kept = kept, removed = removed)
|
||||
Stitch.value(filterResult)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,63 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
object FeatureFilter {
|
||||
|
||||
/**
|
||||
* Builds a Filter using the Feature name as the FilterIdentifier
|
||||
*
|
||||
* @see [[FeatureFilter.fromFeature(identifier, feature)]]
|
||||
*/
|
||||
def fromFeature[Candidate <: UniversalNoun[Any]](
|
||||
feature: Feature[Candidate, Boolean]
|
||||
): Filter[PipelineQuery, Candidate] =
|
||||
FeatureFilter.fromFeature(FilterIdentifier(feature.toString), feature)
|
||||
|
||||
/**
|
||||
* Builds a Filter that keeps candidates when the provided Boolean Feature is present and True.
|
||||
* If the Feature is missing or False, the candidate is removed.
|
||||
*
|
||||
* {{{
|
||||
* Filter.fromFeature(
|
||||
* FilterIdentifier("SomeFilter"),
|
||||
* feature = SomeFeature
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param identifier A FilterIdentifier for the new filter
|
||||
* @param feature A feature of [Candidate, Boolean] type used to determine whether Candidates will be kept
|
||||
* when this feature is present and true otherwise they will be removed.
|
||||
*/
|
||||
def fromFeature[Candidate <: UniversalNoun[Any]](
|
||||
identifier: FilterIdentifier,
|
||||
feature: Feature[Candidate, Boolean]
|
||||
): Filter[PipelineQuery, Candidate] = {
|
||||
val i = identifier
|
||||
|
||||
new Filter[PipelineQuery, Candidate] {
|
||||
override val identifier: FilterIdentifier = i
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (keptCandidates, removedCandidates) = candidates.partition { filterCandidate =>
|
||||
filterCandidate.features.getOrElse(feature, false)
|
||||
}
|
||||
|
||||
Stitch.value(
|
||||
FilterResult(
|
||||
kept = keptCandidates.map(_.candidate),
|
||||
removed = removedCandidates.map(_.candidate)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,64 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.filter.FeatureConditionalFilter.IdentifierInfix
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Predicate to apply to candidate feature, to determine whether to apply filter.
|
||||
* True indicates we will apply the filter. False indicates to keep candidate and not apply filter.
|
||||
* @tparam FeatureValue
|
||||
*/
|
||||
trait ShouldApplyFilter[FeatureValue] {
|
||||
def apply(feature: FeatureValue): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that applies the [[filter]] for candidates for which [[shouldApplyFilter]] is true, and keeps the others
|
||||
* @param feature feature to determine whether to apply underyling filter
|
||||
* @param shouldApplyFilter function to determine whether to apply filter
|
||||
* @param filter the actual filter to apply if shouldApplyFilter is True
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Candidate The type of the candidates
|
||||
* @tparam FeatureValueType
|
||||
*/
|
||||
case class FeatureValueConditionalFilter[
|
||||
-Query <: PipelineQuery,
|
||||
Candidate <: UniversalNoun[Any],
|
||||
FeatureValueType
|
||||
](
|
||||
feature: Feature[Candidate, FeatureValueType],
|
||||
shouldApplyFilter: ShouldApplyFilter[FeatureValueType],
|
||||
filter: Filter[Query, Candidate])
|
||||
extends Filter[Query, Candidate] {
|
||||
override val identifier: FilterIdentifier = FilterIdentifier(
|
||||
feature.toString + IdentifierInfix + filter.identifier.name
|
||||
)
|
||||
|
||||
override val alerts: Seq[Alert] = filter.alerts
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (candidatesToFilter, candidatesToKeep) = candidates.partition { candidate =>
|
||||
shouldApplyFilter(candidate.features.get(feature))
|
||||
}
|
||||
filter.apply(query, candidatesToFilter).map { filterResult =>
|
||||
FilterResult(
|
||||
kept = filterResult.kept ++ candidatesToKeep.map(_.candidate),
|
||||
removed = filterResult.removed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FeatureConditionalFilter {
|
||||
val IdentifierInfix = "FeatureConditional"
|
||||
}
|
Binary file not shown.
@ -1,27 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetAuthorIdFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A filter that checks for presence of a successfully hydrated [[TweetAuthorIdFeature]]
|
||||
*/
|
||||
case class HasAuthorIdFeatureFilter[Candidate <: TweetCandidate]()
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier = FilterIdentifier("HasAuthorIdFeature")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (kept, removed) = candidates.partition(_.features.getTry(TweetAuthorIdFeature).isReturn)
|
||||
Stitch.value(FilterResult(kept.map(_.candidate), removed.map(_.candidate)))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,41 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.filter.ParamGatedFilter.IdentifierPrefix
|
||||
import com.twitter.product_mixer.core.functional_component.common.alert.Alert
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.Conditionally
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
|
||||
/**
|
||||
* A [[Filter]] with [[Conditionally]] based on a [[Param]]
|
||||
*
|
||||
* @param enabledParam the param to turn this filter on and off
|
||||
* @param filter the underlying filter to run when `enabledParam` is true
|
||||
* @tparam Query The domain model for the query or request
|
||||
* @tparam Candidate The type of the candidates
|
||||
*/
|
||||
case class ParamGatedFilter[-Query <: PipelineQuery, Candidate <: UniversalNoun[Any]](
|
||||
enabledParam: Param[Boolean],
|
||||
filter: Filter[Query, Candidate])
|
||||
extends Filter[Query, Candidate]
|
||||
with Filter.Conditionally[Query, Candidate] {
|
||||
override val identifier: FilterIdentifier = FilterIdentifier(
|
||||
IdentifierPrefix + filter.identifier.name)
|
||||
override val alerts: Seq[Alert] = filter.alerts
|
||||
override def onlyIf(query: Query, candidates: Seq[CandidateWithFeatures[Candidate]]): Boolean =
|
||||
Conditionally.and(Filter.Input(query, candidates), filter, query.params(enabledParam))
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = filter.apply(query, candidates)
|
||||
}
|
||||
|
||||
object ParamGatedFilter {
|
||||
val IdentifierPrefix = "ParamGated"
|
||||
}
|
Binary file not shown.
@ -1,63 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Predicate which will be applied to each candidate. True indicates that the candidate will be
|
||||
* @tparam Candidate - the type of the candidate
|
||||
*/
|
||||
trait ShouldKeepCandidate[Candidate] {
|
||||
def apply(candidate: Candidate): Boolean
|
||||
}
|
||||
|
||||
object PredicateFilter {
|
||||
|
||||
/**
|
||||
* Builds a simple Filter out of a predicate function from the candidate to a boolean. For clarity,
|
||||
* we recommend including the name of the shouldKeepCandidate parameter.
|
||||
*
|
||||
* {{{
|
||||
* Filter.fromPredicate(
|
||||
* FilterIdentifier("SomeFilter"),
|
||||
* shouldKeepCandidate = { candidate: UserCandidate => candidate.id % 2 == 0L }
|
||||
* )
|
||||
* }}}
|
||||
*
|
||||
* @param identifier A FilterIdentifier for the new filter
|
||||
* @param shouldKeepCandidate A predicate function from the candidate. Candidates will be kept
|
||||
* when this function returns True.
|
||||
*/
|
||||
def fromPredicate[Candidate <: UniversalNoun[Any]](
|
||||
identifier: FilterIdentifier,
|
||||
shouldKeepCandidate: ShouldKeepCandidate[Candidate]
|
||||
): Filter[PipelineQuery, Candidate] = {
|
||||
val i = identifier
|
||||
|
||||
new Filter[PipelineQuery, Candidate] {
|
||||
override val identifier: FilterIdentifier = i
|
||||
|
||||
/**
|
||||
* Filter the list of candidates
|
||||
*
|
||||
* @return a FilterResult including both the list of kept candidate and the list of removed candidates
|
||||
*/
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (keptCandidates, removedCandidates) = candidates.map(_.candidate).partition {
|
||||
filterCandidate =>
|
||||
shouldKeepCandidate(filterCandidate)
|
||||
}
|
||||
|
||||
Stitch.value(FilterResult(kept = keptCandidates, removed = removedCandidates))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,42 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.snowflake.id.SnowflakeId
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.timelines.configapi.Param
|
||||
import com.twitter.util.Duration
|
||||
|
||||
/**
|
||||
* @param maxAgeParam Feature Switch configurable for convenience
|
||||
* @tparam Candidate The type of the candidates
|
||||
*/
|
||||
case class SnowflakeIdAgeFilter[Candidate <: UniversalNoun[Long]](
|
||||
maxAgeParam: Param[Duration])
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("SnowflakeIdAge")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val maxAge = query.params(maxAgeParam)
|
||||
|
||||
val (keptCandidates, removedCandidates) = candidates
|
||||
.map(_.candidate)
|
||||
.partition { filterCandidate =>
|
||||
SnowflakeId.timeFromIdOpt(filterCandidate.id) match {
|
||||
case Some(creationTime) =>
|
||||
query.queryTime.since(creationTime) <= maxAge
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
Stitch.value(FilterResult(kept = keptCandidates, removed = removedCandidates))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,47 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A [[filter]] that filters candidates based on a country code feature
|
||||
*
|
||||
* @param countryCodeFeature the feature to filter candidates on
|
||||
*/
|
||||
case class TweetAuthorCountryFilter[Candidate <: BaseTweetCandidate](
|
||||
countryCodeFeature: Feature[Candidate, Option[String]])
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetAuthorCountry")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val userCountry = query.getCountryCode
|
||||
|
||||
val (keptCandidates, removedCandidates) = candidates.partition { filteredCandidate =>
|
||||
val authorCountry = filteredCandidate.features.get(countryCodeFeature)
|
||||
|
||||
(authorCountry, userCountry) match {
|
||||
case (Some(authorCountryCode), Some(userCountryCode)) =>
|
||||
authorCountryCode.equalsIgnoreCase(userCountryCode)
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
Stitch.value(
|
||||
FilterResult(
|
||||
kept = keptCandidates.map(_.candidate),
|
||||
removed = removedCandidates.map(_.candidate)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,37 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TweetAuthorIdFeature
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A [[filter]] that filters based on whether query user is the author of the tweet. This will NOT filter empty user ids
|
||||
* @note It is recommended to apply [[HasAuthorIdFeatureFilter]] before this, as this will FAIL if feature is unavailable
|
||||
*
|
||||
* @tparam Candidate The type of the candidates
|
||||
*/
|
||||
case class TweetAuthorIsSelfFilter[Candidate <: BaseTweetCandidate]()
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetAuthorIsSelf")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
val (kept, removed) = candidates.partition { candidate =>
|
||||
val authorId = candidate.features.get(TweetAuthorIdFeature)
|
||||
!query.getOptionalUserId.contains(authorId)
|
||||
}
|
||||
|
||||
val filterResult = FilterResult(
|
||||
kept = kept.map(_.candidate),
|
||||
removed = removed.map(_.candidate)
|
||||
)
|
||||
Stitch.value(filterResult)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,36 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.candidate.tweet_tweetypie.IsReplyFeature
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Filters out tweets that is a reply to a tweet
|
||||
*/
|
||||
case class TweetIsNotReplyFilter[Candidate <: BaseTweetCandidate]()
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetIsNotReply")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val (kept, removed) = candidates
|
||||
.partition { candidate =>
|
||||
!candidate.features.get(IsReplyFeature)
|
||||
}
|
||||
|
||||
val filterResult = FilterResult(
|
||||
kept = kept.map(_.candidate),
|
||||
removed = removed.map(_.candidate)
|
||||
)
|
||||
|
||||
Stitch.value(filterResult)
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
@ -1,40 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
case class TweetLanguageFilter[Candidate <: BaseTweetCandidate](
|
||||
languageCodeFeature: Feature[Candidate, Option[String]])
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetLanguage")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val userAppLanguage = query.getLanguageCode
|
||||
|
||||
val (keptCandidates, removedCandidates) = candidates.partition { filterCandidate =>
|
||||
val tweetLanguage = filterCandidate.features.get(languageCodeFeature)
|
||||
|
||||
(tweetLanguage, userAppLanguage) match {
|
||||
case (Some(tweetLanguageCode), Some(userAppLanguageCode)) =>
|
||||
tweetLanguageCode.equalsIgnoreCase(userAppLanguageCode)
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
Stitch.value(
|
||||
FilterResult(
|
||||
kept = keptCandidates.map(_.candidate),
|
||||
removed = removedCandidates.map(_.candidate)))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,71 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.util.logging.Logging
|
||||
import com.twitter.product_mixer.component_library.filter.TweetVisibilityFilter._
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.spam.rtf.thriftscala.SafetyLevel
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.stitch.tweetypie.{TweetyPie => TweetypieStitchClient}
|
||||
import com.twitter.tweetypie.{thriftscala => TP}
|
||||
import com.twitter.util.Return
|
||||
import com.twitter.util.Try
|
||||
|
||||
object TweetVisibilityFilter {
|
||||
val DefaultTweetIncludes = Set(TP.TweetInclude.TweetFieldId(TP.Tweet.IdField.id))
|
||||
private final val getTweetFieldsFailureMessage = "TweetyPie.getTweetFields failed: "
|
||||
}
|
||||
|
||||
case class TweetVisibilityFilter[Candidate <: BaseTweetCandidate](
|
||||
tweetypieStitchClient: TweetypieStitchClient,
|
||||
tweetVisibilityPolicy: TP.TweetVisibilityPolicy,
|
||||
safetyLevel: SafetyLevel,
|
||||
tweetIncludes: Set[TP.TweetInclude.TweetFieldId] = DefaultTweetIncludes)
|
||||
extends Filter[PipelineQuery, Candidate]
|
||||
with Logging {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetVisibility")
|
||||
|
||||
def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
Stitch
|
||||
.traverse(candidates.map(_.candidate.id)) { tweetId =>
|
||||
tweetypieStitchClient
|
||||
.getTweetFields(tweetId, getTweetFieldsOptions(query.getOptionalUserId))
|
||||
.liftToTry
|
||||
}
|
||||
.map { getTweetFieldsResults: Seq[Try[TP.GetTweetFieldsResult]] =>
|
||||
val (checkedSucceeded, checkFailed) = getTweetFieldsResults.partition(_.isReturn)
|
||||
checkFailed.foreach(e => warn(() => getTweetFieldsFailureMessage, e.throwable))
|
||||
if (checkFailed.nonEmpty) {
|
||||
warn(() =>
|
||||
s"TweetVisibilityFilter dropped ${checkFailed.size} candidates due to tweetypie failure.")
|
||||
}
|
||||
|
||||
val allowedTweets = checkedSucceeded.collect {
|
||||
case Return(TP.GetTweetFieldsResult(_, TP.TweetFieldsResultState.Found(found), _, _)) =>
|
||||
found.tweet.id
|
||||
}.toSet
|
||||
|
||||
val (kept, removed) =
|
||||
candidates.map(_.candidate).partition(candidate => allowedTweets.contains(candidate.id))
|
||||
|
||||
FilterResult(kept = kept, removed = removed)
|
||||
}
|
||||
}
|
||||
|
||||
private def getTweetFieldsOptions(userId: Option[Long]) =
|
||||
TP.GetTweetFieldsOptions(
|
||||
forUserId = userId,
|
||||
tweetIncludes = tweetIncludes.toSet,
|
||||
doNotCache = true,
|
||||
visibilityPolicy = tweetVisibilityPolicy,
|
||||
safetyLevel = Some(safetyLevel)
|
||||
)
|
||||
}
|
Binary file not shown.
@ -1,32 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.cursor.UrtUnorderedExcludeIdsCursor
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.HasPipelineCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
case class UrtUnorderedExcludeIdsCursorFilter[
|
||||
Candidate <: UniversalNoun[Long],
|
||||
Query <: PipelineQuery with HasPipelineCursor[UrtUnorderedExcludeIdsCursor]
|
||||
]() extends Filter[Query, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("UnorderedExcludeIdsCursor")
|
||||
|
||||
override def apply(
|
||||
query: Query,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val excludeIds = query.pipelineCursor.map(_.excludedIds.toSet).getOrElse(Set.empty)
|
||||
val (kept, removed) =
|
||||
candidates.map(_.candidate).partition(candidate => !excludeIds.contains(candidate.id))
|
||||
|
||||
val filterResult = FilterResult(kept = kept, removed = removed)
|
||||
Stitch.value(filterResult)
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
"src/thrift/com/twitter/socialgraph:thrift-scala",
|
||||
"strato/config/columns/lists/reads:core-strato-client",
|
||||
"strato/src/main/scala/com/twitter/strato/client",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
"strato/config/columns/lists/reads:core-strato-client",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,52 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter.list_visibility
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.candidate.TwitterListCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.UniversalNoun
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.socialgraph.thriftscala.SocialgraphList
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.strato.catalog.Fetch
|
||||
import com.twitter.strato.generated.client.lists.reads.CoreOnListClientColumn
|
||||
|
||||
/* This Filter queries the core.List.strato column
|
||||
* on Strato, and filters out any lists that are not
|
||||
* returned. core.List.strato performs an authorization
|
||||
* check, and does not return lists the viewer is not authorized
|
||||
* to have access to. */
|
||||
class ListVisibilityFilter[Candidate <: UniversalNoun[Long]](
|
||||
listsColumn: CoreOnListClientColumn)
|
||||
extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("ListVisibility")
|
||||
|
||||
def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
val listCandidates = candidates.collect {
|
||||
case CandidateWithFeatures(candidate: TwitterListCandidate, _) => candidate
|
||||
}
|
||||
|
||||
Stitch
|
||||
.traverse(
|
||||
listCandidates.map(_.id)
|
||||
) { listId =>
|
||||
listsColumn.fetcher.fetch(listId)
|
||||
}.map { fetchResults =>
|
||||
fetchResults.collect {
|
||||
case Fetch.Result(Some(list: SocialgraphList), _) => list.id
|
||||
}
|
||||
}.map { allowedListIds =>
|
||||
val (kept, excluded) = candidates.map(_.candidate).partition {
|
||||
case candidate: TwitterListCandidate => allowedListIds.contains(candidate.id)
|
||||
case _ => true
|
||||
}
|
||||
FilterResult(kept, excluded)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/feature_hydrator/query/impressed_tweets",
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/candidate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/filter",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,37 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.filter.tweet_impression
|
||||
|
||||
import com.twitter.product_mixer.component_library.feature_hydrator.query.impressed_tweets.ImpressedTweets
|
||||
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
||||
import com.twitter.product_mixer.core.functional_component.filter.Filter
|
||||
import com.twitter.product_mixer.core.functional_component.filter.FilterResult
|
||||
import com.twitter.product_mixer.core.model.common.CandidateWithFeatures
|
||||
import com.twitter.product_mixer.core.model.common.identifier.FilterIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Filters out tweets that the user has seen
|
||||
*/
|
||||
case class TweetImpressionFilter[Candidate <: BaseTweetCandidate](
|
||||
) extends Filter[PipelineQuery, Candidate] {
|
||||
|
||||
override val identifier: FilterIdentifier = FilterIdentifier("TweetImpression")
|
||||
|
||||
override def apply(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithFeatures[Candidate]]
|
||||
): Stitch[FilterResult[Candidate]] = {
|
||||
|
||||
// Set of Tweets that have impressed the user
|
||||
val impressedTweetsSet: Set[Long] = query.features match {
|
||||
case Some(featureMap) => featureMap.getOrElse(ImpressedTweets, Seq.empty).toSet
|
||||
case None => Set.empty
|
||||
}
|
||||
|
||||
val (keptCandidates, removedCandidates) = candidates.partition { filteredCandidate =>
|
||||
!impressedTweetsSet.contains(filteredCandidate.candidate.id)
|
||||
}
|
||||
|
||||
Stitch.value(FilterResult(keptCandidates.map(_.candidate), removedCandidates.map(_.candidate)))
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
scala_library(
|
||||
sources = ["*.scala"],
|
||||
compiler_option_sets = ["fatal_warnings"],
|
||||
strict_deps = True,
|
||||
tags = ["bazel-compatible"],
|
||||
dependencies = [
|
||||
"product-mixer/component-library/src/main/scala/com/twitter/product_mixer/component_library/model/query/ads",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/gate",
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/quality_factor",
|
||||
"src/scala/com/twitter/ml/featurestore/lib",
|
||||
],
|
||||
exports = [
|
||||
"product-mixer/core/src/main/scala/com/twitter/product_mixer/core/functional_component/common",
|
||||
],
|
||||
)
|
Binary file not shown.
Binary file not shown.
@ -1,13 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
object DefinedCountryCodeGate extends Gate[PipelineQuery] {
|
||||
override val identifier: GateIdentifier = GateIdentifier("DefinedCountryCode")
|
||||
|
||||
override def shouldContinue(query: PipelineQuery): Stitch[Boolean] =
|
||||
Stitch.value(query.getCountryCode.isDefined)
|
||||
}
|
Binary file not shown.
@ -1,83 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.feature.featuremap.MissingFeatureException
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.functional_component.gate.GateResult
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.MisconfiguredFeatureMapFailure
|
||||
import com.twitter.product_mixer.core.pipeline.pipeline_failure.PipelineFailure
|
||||
import com.twitter.stitch.Stitch
|
||||
import com.twitter.util.Return
|
||||
import com.twitter.util.Throw
|
||||
|
||||
trait ShouldContinue[Value] {
|
||||
|
||||
/** Given the [[Feature]] value, returns whether the execution should continue */
|
||||
def apply(featureValue: Value): Boolean
|
||||
|
||||
/** If the [[Feature]] is a failure, use this value */
|
||||
def onFailedFeature(t: Throwable): GateResult = GateResult.Stop
|
||||
|
||||
/**
|
||||
* If the [[Feature]], or [[com.twitter.product_mixer.core.feature.featuremap.FeatureMap]],
|
||||
* is missing use this value
|
||||
*/
|
||||
def onMissingFeature: GateResult = GateResult.Stop
|
||||
}
|
||||
|
||||
object FeatureGate {
|
||||
|
||||
def fromFeature(
|
||||
feature: Feature[_, Boolean]
|
||||
): FeatureGate[Boolean] =
|
||||
FeatureGate.fromFeature(GateIdentifier(feature.toString), feature)
|
||||
|
||||
def fromNegatedFeature(
|
||||
feature: Feature[_, Boolean]
|
||||
): FeatureGate[Boolean] =
|
||||
FeatureGate.fromNegatedFeature(GateIdentifier(feature.toString), feature)
|
||||
|
||||
def fromFeature(
|
||||
gateIdentifier: GateIdentifier,
|
||||
feature: Feature[_, Boolean]
|
||||
): FeatureGate[Boolean] =
|
||||
FeatureGate[Boolean](gateIdentifier, feature, identity)
|
||||
|
||||
def fromNegatedFeature(
|
||||
gateIdentifier: GateIdentifier,
|
||||
feature: Feature[_, Boolean]
|
||||
): FeatureGate[Boolean] =
|
||||
FeatureGate[Boolean](gateIdentifier, feature, !identity(_))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A [[Gate]] that is actuated based upon the value of the provided feature
|
||||
*/
|
||||
case class FeatureGate[Value](
|
||||
gateIdentifier: GateIdentifier,
|
||||
feature: Feature[_, Value],
|
||||
continue: ShouldContinue[Value])
|
||||
extends Gate[PipelineQuery] {
|
||||
|
||||
override val identifier: GateIdentifier = gateIdentifier
|
||||
|
||||
override def shouldContinue(query: PipelineQuery): Stitch[Boolean] = {
|
||||
Stitch
|
||||
.value(
|
||||
query.features.map(_.getTry(feature)) match {
|
||||
case Some(Return(value)) => continue(value)
|
||||
case Some(Throw(_: MissingFeatureException)) => continue.onMissingFeature.continue
|
||||
case Some(Throw(t)) => continue.onFailedFeature(t).continue
|
||||
case None =>
|
||||
throw PipelineFailure(
|
||||
MisconfiguredFeatureMapFailure,
|
||||
"Expected a FeatureMap to be present but none was found, ensure that your" +
|
||||
"PipelineQuery has a FeatureMap configured before gating on Feature values"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,19 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.HasPipelineCursor
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* Gate used in first page. Use request cursor to determine if the gate should be open or closed.
|
||||
*/
|
||||
object FirstPageGate extends Gate[PipelineQuery with HasPipelineCursor[_]] {
|
||||
|
||||
override val identifier: GateIdentifier = GateIdentifier("FirstPage")
|
||||
|
||||
// If cursor is first page, then gate should return continue, otherwise return stop
|
||||
override def shouldContinue(query: PipelineQuery with HasPipelineCursor[_]): Stitch[Boolean] =
|
||||
Stitch.value(query.isFirstPage)
|
||||
}
|
Binary file not shown.
@ -1,21 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.functional_component.gate.QueryAndCandidateGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.presentation.CandidateWithDetails
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A Gate that only continues if the previously returned candidates are empty. This is useful
|
||||
* for gating dependent candidate pipelines that are intedned to be used as a backfill when there
|
||||
* are no candidates available.
|
||||
*/
|
||||
case class NoCandidatesGate(scope: CandidateScope) extends QueryAndCandidateGate[PipelineQuery] {
|
||||
override val identifier: GateIdentifier = GateIdentifier("NoCandidates")
|
||||
override def shouldContinue(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithDetails]
|
||||
): Stitch[Boolean] = Stitch.value(scope.partition(candidates).candidatesInScope.isEmpty)
|
||||
}
|
Binary file not shown.
@ -1,16 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.component_library.model.query.ads.AdsQuery
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
object NonEmptyAdsQueryStringGate extends Gate[PipelineQuery with AdsQuery] {
|
||||
override val identifier: GateIdentifier = GateIdentifier("NonEmptyAdsQueryString")
|
||||
|
||||
override def shouldContinue(query: PipelineQuery with AdsQuery): Stitch[Boolean] = {
|
||||
val queryString = query.searchRequestContext.flatMap(_.queryString)
|
||||
Stitch.value(queryString.exists(_.trim.nonEmpty))
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,22 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.functional_component.gate.QueryAndCandidateGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.presentation.CandidateWithDetails
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A Gate that only continues if the previously returned candidates are not empty. This is useful
|
||||
* for gating dependent candidate pipelines that are intended to only be used if a previous pipeline
|
||||
* completed successfully.
|
||||
*/
|
||||
case class NonEmptyCandidatesGate(scope: CandidateScope)
|
||||
extends QueryAndCandidateGate[PipelineQuery] {
|
||||
override val identifier: GateIdentifier = GateIdentifier("NonEmptyCandidates")
|
||||
override def shouldContinue(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithDetails]
|
||||
): Stitch[Boolean] = Stitch.value(scope.partition(candidates).candidatesInScope.nonEmpty)
|
||||
}
|
Binary file not shown.
@ -1,25 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate
|
||||
|
||||
import com.twitter.product_mixer.core.functional_component.gate.Gate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.ComponentIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.product_mixer.core.quality_factor.HasQualityFactorStatus
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A Gate that only continues if the quality factor value of the pipeline is above the given
|
||||
* threshold. This is useful for disabling an expensive function when the pipeline is under pressure
|
||||
* (quality factor is low).
|
||||
*/
|
||||
case class QualityFactorGate(pipelineIdentifier: ComponentIdentifier, threshold: Double)
|
||||
extends Gate[PipelineQuery with HasQualityFactorStatus] {
|
||||
|
||||
override val identifier: GateIdentifier = GateIdentifier(
|
||||
s"${pipelineIdentifier.name}QualityFactor")
|
||||
|
||||
override def shouldContinue(
|
||||
query: PipelineQuery with HasQualityFactorStatus
|
||||
): Stitch[Boolean] =
|
||||
Stitch.value(query.getQualityFactorCurrentValue(pipelineIdentifier) >= threshold)
|
||||
}
|
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
package com.twitter.product_mixer.component_library.gate.any_candidates_without_feature
|
||||
|
||||
import com.twitter.product_mixer.core.feature.Feature
|
||||
import com.twitter.product_mixer.core.functional_component.common.CandidateScope
|
||||
import com.twitter.product_mixer.core.functional_component.gate.QueryAndCandidateGate
|
||||
import com.twitter.product_mixer.core.model.common.identifier.GateIdentifier
|
||||
import com.twitter.product_mixer.core.model.common.presentation.CandidateWithDetails
|
||||
import com.twitter.product_mixer.core.pipeline.PipelineQuery
|
||||
import com.twitter.stitch.Stitch
|
||||
|
||||
/**
|
||||
* A gate that enables a component only if any candidates are missing a specific feature.
|
||||
* You can restrict which candidates to check with the scope parameter.
|
||||
* This is most commonly used to do backfill scoring, where you can have one Scoring Pipeline that
|
||||
* might return a score feature "FeatureA" and another sequential pipeline that you only want to run
|
||||
* if the previous scoring pipeline fails to hydrate for all candidates.
|
||||
* @param identifier Unique identifier for this gate. Typically, AnyCandidatesWithout{YourFeature}.
|
||||
* @param scope A [[CandidateScope]] to specify which candidates to check.
|
||||
* @param missingFeature The feature that should be missing for any of the candidates for this gate to continue
|
||||
*/
|
||||
case class AnyCandidatesWithoutFeatureGate(
|
||||
override val identifier: GateIdentifier,
|
||||
scope: CandidateScope,
|
||||
missingFeature: Feature[_, _])
|
||||
extends QueryAndCandidateGate[PipelineQuery] {
|
||||
|
||||
override def shouldContinue(
|
||||
query: PipelineQuery,
|
||||
candidates: Seq[CandidateWithDetails]
|
||||
): Stitch[Boolean] =
|
||||
Stitch.value(scope.partition(candidates).candidatesInScope.exists { candidateWithDetails =>
|
||||
!candidateWithDetails.features.getSuccessfulFeatures.contains(missingFeature)
|
||||
})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user