# 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=hidden` are 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

```dotenv
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.
