Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions home-mixer/candidate_hydrators/in_network_candidate_hydrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,79 @@ impl Hydrator<ScoredPostsQuery, PostCandidate> for InNetworkCandidateHydrator {
candidate.in_network = hydrated.in_network;
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::models::user_features::UserFeatures;

fn query_following(viewer_id: u64, followed: Vec<i64>) -> ScoredPostsQuery {
ScoredPostsQuery {
user_id: viewer_id,
user_features: UserFeatures {
followed_user_ids: followed,
..Default::default()
},
..Default::default()
}
}

async fn stamp(query: &ScoredPostsQuery, author_id: u64) -> Option<bool> {
let hydrator = InNetworkCandidateHydrator;
let candidates = vec![PostCandidate {
tweet_id: 1,
author_id,
..Default::default()
}];
let hydrated = hydrator.hydrate(query, &candidates).await;
let mut candidate = candidates.into_iter().next().unwrap();
hydrator.update(&mut candidate, hydrated.into_iter().next().unwrap().unwrap());
candidate.in_network
}

#[tokio::test]
async fn followed_author_is_in_network() {
let query = query_following(1, vec![42]);
assert_eq!(stamp(&query, 42).await, Some(true));
}

#[tokio::test]
async fn unfollowed_author_is_oon() {
let query = query_following(1, vec![42]);
assert_eq!(stamp(&query, 99).await, Some(false));
}

#[tokio::test]
async fn missing_author_id_is_stamped_oon() {
let query = query_following(1, vec![42]);
assert_eq!(
stamp(&query, 0).await,
Some(false),
"author_id 0 is not in the follow set, so this stamps OON"
);
}

#[tokio::test]
async fn tes_author_id_must_be_present_before_stamp() {
let query = query_following(1, vec![42]);
let hydrator = InNetworkCandidateHydrator;

// Wrong order: stamp while author_id is still 0, then TES fills the followed author.
let mut too_early = PostCandidate {
tweet_id: 1,
author_id: 0,
..Default::default()
};
let early = hydrator.hydrate(&query, &[too_early.clone()]).await;
hydrator.update(&mut too_early, early.into_iter().next().unwrap().unwrap());
too_early.author_id = 42;
assert_eq!(
too_early.in_network,
Some(false),
"stale OON stamp after TES fills a followed author"
);

// Right order: TES author_id first.
assert_eq!(stamp(&query, 42).await, Some(true));
}
}
7 changes: 6 additions & 1 deletion home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,17 @@ impl PhoenixCandidatePipeline {
cached_posts_source,
];

// CoreData must run before InNetwork. TweetMixer (and other thin sources) often
// ship author_id = 0; TES fills it. InNetwork reads author_id and stamps
// in_network forever. Stamping first marks followed authors as OON, so Recs-only
// VF drops (DoNotAmplify, NSFW/spam high-recall, malicious URL) fire on Home
// authors. PhoenixScoresPipeline already uses this order.
let hydrators: Vec<Box<dyn Hydrator<ScoredPostsQuery, PostCandidate>>> = vec![
Box::new(InNetworkCandidateHydrator),
Box::new(BidirectionalFollowHydrator {
socialgraph_client: socialgraph_client.clone(),
}),
Box::new(core_data_hydrator),
Box::new(InNetworkCandidateHydrator),
Box::new(
QuoteHydrator::new(
tes_client.clone(),
Expand Down
1 change: 1 addition & 0 deletions home-mixer/candidate_pipeline/phoenix_scores_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl PhoenixScoresPipeline {
let sources: Vec<Box<dyn Source<ScoredPostsQuery, PostCandidate>>> =
vec![Box::new(SeedCandidatesSource)];

// TES author_id before InNetwork — do not invert. See phoenix_candidate_pipeline.
let hydrators: Vec<Box<dyn Hydrator<ScoredPostsQuery, PostCandidate>>> = vec![
Box::new(CoreDataCandidateHydrator::new(tes_client.clone()).await),
Box::new(InNetworkCandidateHydrator),
Expand Down
1 change: 1 addition & 0 deletions home-mixer/sources/tweet_mixer_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl Source<ScoredPostsQuery, PostCandidate> for TweetMixerSource {
return None;
}

// 0 until CoreData/TES. InNetwork must run after that fill.
let author_id = candidate
.author_id
.and_then(|id| u64::try_from(id).ok())
Expand Down