# For You

## Route

- URL: `GET /discover/for-you`
- Auth required: yes
- Controller: `App\Http\Controllers\Web\DiscoverController::forYou()`
- Entry service: `App\Services\Recommendations\RecommendationFeedResolver`

## High-level flow

`For You` is not a simple search sort.
It is a recommendation pipeline with engine selection, caching, layered candidate generation, reranking, and cursor pagination.

The controller:

1. reads `limit` and `cursor`
2. calls `RecommendationFeedResolver::getFeed()`
3. converts feed items into the artwork card view model
4. returns HTML or JSON depending on request type

## Engine selection

`RecommendationFeedResolver` chooses between two implementations:

- V2: `App\Services\Recommendations\RecommendationServiceV2`
- V1: `App\Services\Recommendations\PersonalizedFeedService`

Selection is based on:

- `config('discovery.v2.enabled')`
- rollout percentage bucket for the current user
- explicit `algo_version` override

## Cache model

Both engines use `user_recommendation_cache`.

At request time:

1. Load cache row for `(user_id, algo_version)`
2. Check cache version and `expires_at`
3. If missing or stale, dispatch `RegenerateUserRecommendationCacheJob`
4. If the row is empty, build fallback recommendations inline for the current request

This means the page is usually cache-backed, but it does not hard-fail if the cache is cold.

## V2 pipeline

V2 is the richer layered engine.

### Candidate layers

The candidate pool is blended from:

- personalized layer
- social layer
- trending layer
- exploration layer
- vector layer (only when V3/vector support is enabled and configured)

Default target ratios from `config/discovery.php`:

- personalized: 50%
- social: 20%
- trending: 20%
- exploration: 10%

### Main V2 score

For each candidate row:

```text
score
  = (base_score * weight_base)
  + session_boost
  + social_boost
  + trending_boost
  + exploration_boost
  + creator_boost
  + vector_boost
  - negative_penalty
  - repetition_penalty
```

Where:

- `session_boost` comes from merged session/profile signals
- `social_boost` comes from followed creators and artworks liked by followed creators
- `trending_boost` is built from `trending_score_1h`, `trending_score_24h`, `trending_score_7d`
- `exploration_boost` rewards fresh uploads, new creators, and unseen tags
- `creator_boost` uses creator follower count plus artwork momentum metrics
- `vector_boost` comes from visual similarity when vector mode is enabled
- `negative_penalty` reflects hidden artworks and disliked tags
- `repetition_penalty` suppresses creator and tag repetition inside one result page

### Trending contribution inside V2

V2 combines the artwork's stored trending columns like this:

```text
trendingBoost
  = (trending_score_1h * weight_1h)
  + (trending_score_24h * weight_24h)
  + (trending_score_7d * weight_7d)
```

Then divides by 100 before merging into the final score.

### Negative signals

V2 reads `user_negative_signals` and applies:

- hidden artwork exclusion
- disliked tag penalty

### Supporting data sources

V2 reads from:

- `user_recommendation_cache`
- session/profile signal builders
- `artworks`
- `artwork_stats`
- `artwork_similarities`
- `artwork_embeddings` and vector service, when enabled
- `user_followers`
- `artwork_favourites`
- `user_negative_signals`

## V1 pipeline

V1 is simpler and category-affinity driven.

It reads `user_interest_profiles`, then scores candidates with a weighted blend:

```text
score = (w1 * affinity)
      + (w2 * recency)
      + (w3 * popularity)
      + (w4 * novelty)
```

Cold start falls back to a blend of popular artworks and `artwork_similarities` seeds.

## Background jobs and schedules

### Directly relevant

- `RegenerateUserRecommendationCacheJob`
  - dispatched on demand when cache is missing or stale

### Support jobs for candidate quality

- `RecBuildItemPairsFromFavouritesJob` every 4 hours
- `RecComputeSimilarByTagsJob` daily at 02:00
- `RecComputeSimilarByBehaviorJob` daily at 02:15
- `RecComputeSimilarHybridJob` daily at 02:30
- `analytics:aggregate-discovery-feedback` daily at 03:25

These jobs do not directly render the page, but they improve the offline inputs and behavioral data used by the recommender.

## Cache behavior

- V1 cache TTL default: 60 minutes
- V2 cache TTL default: 15 minutes

Cursor pagination is offset-based under the hood.

## Notes

- `For You` is the most configuration-sensitive page in this set.
- What a given user sees can differ by rollout bucket, `algo_version`, cache state, and whether V2/V3 features are enabled.
- If you are debugging a single user's page, inspect `RecommendationFeedResolver::inspectDecision()` first.