[docx] split commit for file 2200

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

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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)")

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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",
],
)

View File

@ -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()
}
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}

View File

@ -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)))
}
}
}
}

View File

@ -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"
}

View File

@ -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)))
}
}

View File

@ -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"
}

View File

@ -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))
}
}
}
}

View File

@ -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))
}
}

View File

@ -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)
)
)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)))
}
}

View File

@ -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)
)
}

View File

@ -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)
}
}

View File

@ -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",
],
)

View File

@ -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)
}
}
}

View File

@ -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",
],
)

View File

@ -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)))
}
}

View File

@ -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",
],
)

View File

@ -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)
}

View File

@ -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"
)
}
)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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