3.0 KiB
Home feed ranking
The home feed contract remains GET /api/v1/feed/me; no query parameter or response field was removed or renamed.
Candidate generation
Each request merges two bounded candidate pools:
- Recent eligible posts (65%).
- High-quality eligible posts ranked by saves, shares, comments, and likes (35%).
The merged pool is deduplicated and capped between 400 and 1,000 candidates depending on the requested page size. The default feed remains focused on followed accounts and falls back to public discovery only when the default following feed is empty.
Hard safety filters
The ranking layer cannot override these rules:
- Deleted and archived posts are excluded.
- Posts with
moderationStatus=hiddenare excluded. - Blocked or blocking authors are excluded.
- Disabled and missing authors are excluded after population.
- Posts reported by the current viewer are excluded unless the report was rejected.
- Public, followers-only, and own-post visibility rules remain enforced.
Personalized signals
A short-lived ranking profile is computed from the viewer's last 90 days of:
- Likes (weight 1.5).
- Comments (weight 3).
- Saves (weight 4).
- Shares (weight 5).
Those events produce capped affinities for authors, post types, and hashtags. Existing profile preferences, follows, location, and requested post type remain signals. The profile is cached for 30 seconds by default.
Scoring
Raw counters use log1p normalization so viral totals cannot grow without bound and erase personalization. Ranking combines:
- Log-normalized engagement quality.
- Exponential freshness decay with a small boost for posts younger than six hours.
- Follow, author affinity, post-type affinity, and hashtag affinity.
- Profile interests and a bounded geographic boost.
- Small verification and follower-count priors.
- A penalty for posts the viewer already interacted with, reducing repetition without hiding them.
Diversity
After relevance scoring, a deterministic greedy reranker applies penalties for:
- Consecutive posts from the same author.
- Repeated appearances by the same author.
- Overrepresented post types.
- Repeated hashtags.
This preserves strong content while avoiding a page dominated by one creator or one format.
Cursor stability
New cursors contain an opaque version, offset, and ranking timestamp. Every page in the cursor chain applies the same createdAt <= rankedAt window and computes freshness against the same timestamp, so newly created posts do not shift already paginated results. Legacy numeric offset cursors remain supported.
Configuration
FEED_CACHE_ENABLED=true
FEED_CACHE_USER_TTL_SECONDS=15
FEED_CACHE_TRENDING_TTL_SECONDS=30
FEED_CACHE_RANKING_PROFILE_TTL_SECONDS=30
Viewer-level watch duration and explicit "Not interested" events do not exist in the current data model. They should be added as future ranking signals when the mobile client starts emitting those events; global viewCount and playCount are used only as weak quality priors.