the-algorithm/visibilitylib/src/main/scala/com/twitter/visibility/rules/generators/TweetRuleGenerator.scala

274 lines
11 KiB
Scala

package com.twitter.visibility.rules.generators
import com.twitter.visibility.models.SafetyLevel
import com.twitter.visibility.models.SafetyLevelGroup
import com.twitter.visibility.models.ViolationLevel
import com.twitter.visibility.rules.FreedomOfSpeechNotReachActions
import com.twitter.visibility.rules.FreedomOfSpeechNotReachRules
import com.twitter.visibility.rules.Rule
import com.twitter.visibility.rules.generators.TweetRuleGenerator.violationLevelPolicies
object TweetRuleGenerator {
private val level3LimitedActions: Seq[String] = Seq(
"like",
"reply",
"retweet",
"quote_tweet",
"share_tweet_via",
"add_to_bookmarks",
"pin_to_profile",
"copy_link",
"send_via_dm")
private val violationLevelPolicies: Map[
ViolationLevel,
Map[UserType, TweetVisibilityPolicy]
] = Map(
ViolationLevel.Level1 -> Map(
UserType.Follower -> TweetVisibilityPolicy
.builder()
.addGlobalRule(FreedomOfSpeechNotReachActions.SoftInterventionAvoidAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Notifications,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Recommendations,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Search,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.TopicRecommendations,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelRule(
SafetyLevel.TimelineHomeRecommendations,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelRule(
SafetyLevel.TrendsRepresentativeTweet,
FreedomOfSpeechNotReachActions.DropAction())
.build,
UserType.Author -> TweetVisibilityPolicy
.builder()
.addGlobalRule(FreedomOfSpeechNotReachActions.AppealableAction())
.build,
UserType.Other -> TweetVisibilityPolicy
.builder()
.addGlobalRule(FreedomOfSpeechNotReachActions.SoftInterventionAvoidAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Notifications,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Recommendations,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.TimelineHome,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.Search,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.TopicRecommendations,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelRule(
SafetyLevel.TrendsRepresentativeTweet,
FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelRule(
SafetyLevel.ConversationReply,
FreedomOfSpeechNotReachActions.SoftInterventionAvoidAbusiveQualityReplyAction())
.build,
),
ViolationLevel.Level3 -> Map(
UserType.Follower -> TweetVisibilityPolicy
.builder()
.addGlobalRule(FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.TimelineProfile,
FreedomOfSpeechNotReachActions.SoftInterventionAvoidLimitedEngagementsAction(
limitedActionStrings = Some(level3LimitedActions))
)
.addSafetyLevelGroupRule(
SafetyLevelGroup.TweetDetails,
FreedomOfSpeechNotReachActions.SoftInterventionAvoidLimitedEngagementsAction(
limitedActionStrings = Some(level3LimitedActions))
)
.addSafetyLevelRule(
SafetyLevel.ConversationReply,
FreedomOfSpeechNotReachActions.SoftInterventionAvoidLimitedEngagementsAction(
limitedActionStrings = Some(level3LimitedActions))
)
.addSafetyLevelRule(
SafetyLevel.ConversationFocalTweet,
FreedomOfSpeechNotReachActions.SoftInterventionAvoidLimitedEngagementsAction(
limitedActionStrings = Some(level3LimitedActions))
)
.build,
UserType.Author -> TweetVisibilityPolicy
.builder()
.addGlobalRule(
FreedomOfSpeechNotReachActions.AppealableAvoidLimitedEngagementsAction(
limitedActionStrings = Some(level3LimitedActions))
)
.build,
UserType.Other -> TweetVisibilityPolicy
.builder()
.addGlobalRule(FreedomOfSpeechNotReachActions.DropAction())
.addSafetyLevelGroupRule(
SafetyLevelGroup.TimelineProfile,
FreedomOfSpeechNotReachActions
.InterstitialLimitedEngagementsAvoidAction(limitedActionStrings =
Some(level3LimitedActions))
)
.addSafetyLevelGroupRule(
SafetyLevelGroup.TweetDetails,
FreedomOfSpeechNotReachActions
.InterstitialLimitedEngagementsAvoidAction(limitedActionStrings =
Some(level3LimitedActions))
)
.addSafetyLevelRule(
SafetyLevel.ConversationReply,
FreedomOfSpeechNotReachActions
.InterstitialLimitedEngagementsAvoidAction(limitedActionStrings =
Some(level3LimitedActions))
)
.addSafetyLevelRule(
SafetyLevel.ConversationFocalTweet,
FreedomOfSpeechNotReachActions
.InterstitialLimitedEngagementsAvoidAction(limitedActionStrings =
Some(level3LimitedActions))
)
.build,
),
)
}
sealed trait UserType
object UserType {
case object Author extends UserType
case object Follower extends UserType
case object Other extends UserType
}
class TweetRuleGenerator extends RuleGenerator {
private[rules] val tweetRulesForSurface: Map[SafetyLevel, Seq[Rule]] = generateTweetPolicies()
private[rules] def getViolationLevelPolicies = violationLevelPolicies
override def rulesForSurface(safetyLevel: SafetyLevel): Seq[Rule] =
tweetRulesForSurface.getOrElse(safetyLevel, Seq())
private def generateRulesForPolicy(
violationLevel: ViolationLevel,
userType: UserType,
tweetVisibilityPolicy: TweetVisibilityPolicy
): Seq[(SafetyLevel, Rule)] = {
tweetVisibilityPolicy
.getRules()
.map {
case (safetyLevel, actionBuilder) =>
safetyLevel -> (userType match {
case UserType.Author =>
FreedomOfSpeechNotReachRules.ViewerIsAuthorAndTweetHasViolationOfLevel(
violationLevel = violationLevel,
actionBuilder = actionBuilder.withViolationLevel(violationLevel = violationLevel))
case UserType.Follower =>
FreedomOfSpeechNotReachRules.ViewerIsFollowerAndTweetHasViolationOfLevel(
violationLevel = violationLevel,
actionBuilder = actionBuilder.withViolationLevel(violationLevel = violationLevel))
case UserType.Other =>
FreedomOfSpeechNotReachRules.ViewerIsNonFollowerNonAuthorAndTweetHasViolationOfLevel(
violationLevel = violationLevel,
actionBuilder = actionBuilder.withViolationLevel(violationLevel = violationLevel))
})
}.toSeq
}
private def generatePoliciesForViolationLevel(
violationLevel: ViolationLevel
): Seq[(SafetyLevel, Rule)] = {
getViolationLevelPolicies
.get(violationLevel).map { policiesPerUserType =>
Seq(UserType.Author, UserType.Follower, UserType.Other).foldLeft(
List.empty[(UserType, SafetyLevel, Rule)]) {
case (rulesForAllUserTypes, userType) =>
rulesForAllUserTypes ++ generateRulesForPolicy(
violationLevel = violationLevel,
userType = userType,
tweetVisibilityPolicy = policiesPerUserType(userType)).map {
case (safetyLevel, rule) => (userType, safetyLevel, rule)
}
}
}
.map(policy => optimizePolicy(policy = policy, violationLevel = violationLevel))
.getOrElse(List())
}
private def injectFallbackRule(rules: Seq[Rule]): Seq[Rule] = {
rules :+ FreedomOfSpeechNotReachRules.TweetHasViolationOfAnyLevelFallbackDropRule
}
private def optimizePolicy(
policy: Seq[(UserType, SafetyLevel, Rule)],
violationLevel: ViolationLevel
): Seq[(SafetyLevel, Rule)] = {
val policiesByUserType = policy.groupBy { case (userType, _, _) => userType }.map {
case (userType, aggregated) =>
(userType, aggregated.map { case (_, safetyLevel, rules) => (safetyLevel, rules) })
}
val followerPolicies = aggregateRulesBySafetyLevel(
policiesByUserType.getOrElse(UserType.Follower, Seq()))
val otherPolicies = aggregateRulesBySafetyLevel(
policiesByUserType.getOrElse(UserType.Other, Seq()))
policiesByUserType(UserType.Author) ++
followerPolicies.collect {
case (safetyLevel, rule) if !otherPolicies.contains(safetyLevel) =>
(safetyLevel, rule)
} ++
otherPolicies.collect {
case (safetyLevel, rule) if !followerPolicies.contains(safetyLevel) =>
(safetyLevel, rule)
} ++
followerPolicies.keySet
.intersect(otherPolicies.keySet).foldLeft(List.empty[(SafetyLevel, Rule)]) {
case (aggr, safetyLevel)
if followerPolicies(safetyLevel).actionBuilder == otherPolicies(
safetyLevel).actionBuilder =>
(
safetyLevel,
FreedomOfSpeechNotReachRules.ViewerIsNonAuthorAndTweetHasViolationOfLevel(
violationLevel = violationLevel,
actionBuilder = followerPolicies(safetyLevel).actionBuilder
)) :: aggr
case (aggr, safetyLevel) =>
(safetyLevel, followerPolicies(safetyLevel)) ::
(safetyLevel, otherPolicies(safetyLevel)) :: aggr
}
}
private def aggregateRulesBySafetyLevel(
policy: Seq[(SafetyLevel, Rule)]
): Map[SafetyLevel, Rule] = {
policy
.groupBy {
case (safetyLevel, _) => safetyLevel
}.map {
case (safetyLevel, Seq((_, rule))) =>
(safetyLevel, rule)
case _ => throw new Exception("Policy optimization failure")
}
}
private def generateTweetPolicies(): Map[SafetyLevel, Seq[Rule]] = {
Seq(ViolationLevel.Level4, ViolationLevel.Level3, ViolationLevel.Level2, ViolationLevel.Level1)
.foldLeft(List.empty[(SafetyLevel, Rule)]) {
case (rulesForAllViolationLevels, violationLevel) =>
rulesForAllViolationLevels ++
generatePoliciesForViolationLevel(violationLevel)
}
.groupBy { case (safetyLevel, _) => safetyLevel }
.map {
case (safetyLevel, list) =>
(safetyLevel, injectFallbackRule(list.map { case (_, rule) => rule }))
}
}
}