mirror of
https://github.com/twitter/the-algorithm.git
synced 2024-06-01 00:38:46 +02:00
b389c3d302
Pushservice is the main recommendation service we use to surface recommendations to our users via notifications. It fetches candidates from various sources, ranks them in order of relevance, and applies filters to determine the best one to send.
148 lines
5.3 KiB
Scala
148 lines
5.3 KiB
Scala
package com.twitter.frigate.pushservice.model
|
|
|
|
import com.twitter.escherbird.common.thriftscala.QualifiedId
|
|
import com.twitter.escherbird.metadata.thriftscala.BasicMetadata
|
|
import com.twitter.escherbird.metadata.thriftscala.EntityIndexFields
|
|
import com.twitter.escherbird.metadata.thriftscala.EntityMegadata
|
|
import com.twitter.finagle.stats.StatsReceiver
|
|
import com.twitter.frigate.common.base.MagicFanoutCandidate
|
|
import com.twitter.frigate.common.base.MagicFanoutEventCandidate
|
|
import com.twitter.frigate.common.base.RichEventFutCandidate
|
|
import com.twitter.frigate.magic_events.thriftscala
|
|
import com.twitter.frigate.magic_events.thriftscala.AnnotationAlg
|
|
import com.twitter.frigate.magic_events.thriftscala.FanoutEvent
|
|
import com.twitter.frigate.magic_events.thriftscala.MagicEventsReason
|
|
import com.twitter.frigate.magic_events.thriftscala.SemanticCoreID
|
|
import com.twitter.frigate.magic_events.thriftscala.SimClusterID
|
|
import com.twitter.frigate.magic_events.thriftscala.TargetID
|
|
import com.twitter.frigate.pushservice.model.PushTypes.PushCandidate
|
|
import com.twitter.hermit.store.semantic_core.SemanticEntityForQuery
|
|
import com.twitter.livevideo.timeline.domain.v2.Event
|
|
import com.twitter.topiclisting.utt.LocalizedEntity
|
|
import com.twitter.util.Future
|
|
|
|
case class FanoutReasonEntities(
|
|
userIds: Set[Long],
|
|
placeIds: Set[Long],
|
|
semanticCoreIds: Set[SemanticCoreID],
|
|
simclusterIds: Set[SimClusterID]) {
|
|
val qualifiedIds: Set[QualifiedId] =
|
|
semanticCoreIds.map(e => QualifiedId(e.domainId, e.entityId))
|
|
}
|
|
|
|
object FanoutReasonEntities {
|
|
val empty = FanoutReasonEntities(
|
|
userIds = Set.empty,
|
|
placeIds = Set.empty,
|
|
semanticCoreIds = Set.empty,
|
|
simclusterIds = Set.empty
|
|
)
|
|
|
|
def from(reasons: Seq[TargetID]): FanoutReasonEntities = {
|
|
val userIds: Set[Long] = reasons.collect {
|
|
case TargetID.UserID(userId) => userId.id
|
|
}.toSet
|
|
val placeIds: Set[Long] = reasons.collect {
|
|
case TargetID.PlaceID(placeId) => placeId.id
|
|
}.toSet
|
|
val semanticCoreIds: Set[SemanticCoreID] = reasons.collect {
|
|
case TargetID.SemanticCoreID(semanticCoreID) => semanticCoreID
|
|
}.toSet
|
|
val simclusterIds: Set[SimClusterID] = reasons.collect {
|
|
case TargetID.SimClusterID(simClusterID) => simClusterID
|
|
}.toSet
|
|
|
|
FanoutReasonEntities(
|
|
userIds = userIds,
|
|
placeIds,
|
|
semanticCoreIds = semanticCoreIds,
|
|
simclusterIds = simclusterIds
|
|
)
|
|
}
|
|
}
|
|
|
|
trait MagicFanoutHydratedCandidate extends PushCandidate with MagicFanoutCandidate {
|
|
lazy val fanoutReasonEntities: FanoutReasonEntities =
|
|
FanoutReasonEntities.from(candidateMagicEventsReasons.map(_.reason))
|
|
}
|
|
|
|
trait MagicFanoutEventHydratedCandidate
|
|
extends MagicFanoutHydratedCandidate
|
|
with MagicFanoutEventCandidate
|
|
with RichEventFutCandidate {
|
|
|
|
def target: PushTypes.Target
|
|
|
|
def stats: StatsReceiver
|
|
|
|
def fanoutEvent: Option[FanoutEvent]
|
|
|
|
def eventFut: Future[Option[Event]]
|
|
|
|
def semanticEntityResults: Map[SemanticEntityForQuery, Option[EntityMegadata]]
|
|
|
|
def effectiveMagicEventsReasons: Option[Seq[MagicEventsReason]]
|
|
|
|
def followedTopicLocalizedEntities: Future[Set[LocalizedEntity]]
|
|
|
|
def ergLocalizedEntities: Future[Set[LocalizedEntity]]
|
|
|
|
lazy val entityAnnotationAlg: Map[TargetID, Set[AnnotationAlg]] =
|
|
fanoutEvent
|
|
.flatMap { metadata =>
|
|
metadata.eventAnnotationInfo.map { eventAnnotationInfo =>
|
|
eventAnnotationInfo.map {
|
|
case (target, annotationInfoSet) => target -> annotationInfoSet.map(_.alg).toSet
|
|
}.toMap
|
|
}
|
|
}.getOrElse(Map.empty)
|
|
|
|
lazy val eventSource: Option[String] = fanoutEvent.map { metadata =>
|
|
val source = metadata.eventSource.getOrElse("undefined")
|
|
stats.scope("eventSource").counter(source).incr()
|
|
source
|
|
}
|
|
|
|
lazy val semanticCoreEntityTags: Map[(Long, Long), Set[String]] =
|
|
semanticEntityResults.flatMap {
|
|
case (semanticEntityForQuery, entityMegadataOpt: Option[EntityMegadata]) =>
|
|
for {
|
|
entityMegadata <- entityMegadataOpt
|
|
basicMetadata: BasicMetadata <- entityMegadata.basicMetadata
|
|
indexableFields: EntityIndexFields <- basicMetadata.indexableFields
|
|
tags <- indexableFields.tags
|
|
} yield {
|
|
((semanticEntityForQuery.domainId, semanticEntityForQuery.entityId), tags.toSet)
|
|
}
|
|
}
|
|
|
|
lazy val owningTwitterUserIds: Seq[Long] = semanticEntityResults.values.flatten
|
|
.flatMap {
|
|
_.basicMetadata.flatMap(_.twitter.flatMap(_.owningTwitterUserIds))
|
|
}.flatten
|
|
.toSeq
|
|
.distinct
|
|
|
|
lazy val eventFanoutReasonEntities: FanoutReasonEntities =
|
|
fanoutEvent match {
|
|
case Some(fanout) =>
|
|
fanout.targets
|
|
.map { targets: Seq[thriftscala.Target] =>
|
|
FanoutReasonEntities.from(targets.flatMap(_.whitelist).flatten)
|
|
}.getOrElse(FanoutReasonEntities.empty)
|
|
case _ => FanoutReasonEntities.empty
|
|
}
|
|
|
|
override lazy val eventResultFut: Future[Event] = eventFut.map {
|
|
case Some(eventResult) => eventResult
|
|
case _ =>
|
|
throw new IllegalArgumentException("event is None for MagicFanoutEventHydratedCandidate")
|
|
}
|
|
override val rankScore: Option[Double] = None
|
|
override val predictionScore: Option[Double] = None
|
|
}
|
|
|
|
case class MagicFanoutEventHydratedInfo(
|
|
fanoutEvent: Option[FanoutEvent],
|
|
semanticEntityResults: Map[SemanticEntityForQuery, Option[EntityMegadata]])
|