Skip to content

[Feed] Add GET /v1/users/{id}/feed/for-you endpoint#797

Closed
dylanjeffers wants to merge 3 commits intomainfrom
claude/silly-brown-adc0e7
Closed

[Feed] Add GET /v1/users/{id}/feed/for-you endpoint#797
dylanjeffers wants to merge 3 commits intomainfrom
claude/silly-brown-adc0e7

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

Summary

Server-side replacement for the client-side blend in apps/packages/common/src/api/tan-query/lineups/useForYouFeed.ts. Adds GET /v1/users/{id}/feed/for-you — a user-scoped For You feed that mirrors the apps' four-source blend on the server.

  • Four candidate streams (recommended, following originals, weekly trending, underground trending) interleaved with the same fixed 10-slot pattern the client uses: [R R R F R T R F U R] → 60% recommended / 20% following / 10% trending / 10% underground. Falls through in priority order when a source is exhausted.
  • Each source capped at 200 candidates. The union is deduped by track_id. Already-saved tracks are excluded at the SQL level rather than re-emitting them as "new" recommendations.
  • Source SQL mirrors the underlying single-source endpoints (v1_users_recommended_tracks.go, v1_users_feed.go filter=original, v1_tracks_trending.go, v1_tracks_trending_underground.go); recommended switched to score-DESC ordering for stable pagination. Liveness/gating/unlisted/deleted filters match those files and v1_events_remix_contests.go.
  • Standard limit/offset pagination applied to the composed list.

Path-param :userId is the requesting user (same auth pattern as /v1/users/:userId/recommended-tracks and /v1/users/:userId/feed).

Consumed by apps' useForYouFeed.ts.

Test plan

  • Unit: blendForYouSources produces canonical [R R R F R T R F U R] ordering when every source is full
  • Unit: empty source falls through to next priority (recommended)
  • Unit: id appearing in multiple sources emitted exactly once
  • Integration: blend produces tracks from all four sources; filtered tracks (deactivated owner / unlisted / deleted / already-saved / played from recommended) are excluded
  • Integration: 400 when :userId is not a valid hash id
  • Integration: already-saved track is filtered
  • Integration: track present in two sources appears exactly once
  • Integration: limit/offset pagination doesn't repeat
  • Integration: 400 for invalid limit/offset (negative, over max, non-numeric)
  • go build ./api/..., go vet ./api/... clean
  • Existing TestV1FeedForYou*, TestV1UsersFeed*, TestV1UserTracks*, TestV1UsersRecommendedTracks*, Test200* still pass

🤖 Generated with Claude Code

dylanjeffers and others added 3 commits May 5, 2026 20:56
…lgorithm

Implements a personalized For You feed modeled on Twitter's 2023
open-sourced timeline pipeline (candidate generation -> ranking ->
filtering -> diversity).

Candidate sources (4):
- in-network: recent uploads from artists the viewer follows
- weekly trending (track_trending_scores, time_range=week)
- underground trending (sub-1500 follower/following artists)
- similar-artist 1-hop CF: artists co-saved by users who saved my saved
  artists' tracks

Ranking (SQL-side):
- 48h half-life recency: EXP(-LN(2) * hours_old / 48)
- engagement: LN(1 + 3*saves + 2*reposts + plays) (saves > reposts > plays)
- social affinity: 1 + min(LN(1 + my_engagement_count) / 4, 1)
- source weight: in-network 1.20, trending 1.00, underground 0.95, similar 0.90

Filtering / diversity:
- hard filters mirror the v1_events_remix_contests.go pattern:
  is_delete=false, is_unlisted=false, is_available=true, stem_of IS NULL,
  no access_authorities, owner not deactivated
- excludes tracks the viewer has already saved
- 3-per-artist cap via ROW_NUMBER() OVER (PARTITION BY owner_id)
- Go-side greedy diversity pass with a 5-track lookahead to avoid
  consecutive same-artist tracks without disturbing global rank

Pagination: user_id (required), limit (1-100, default 25), offset (0-200).

Consumed by apps#14237.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the OpenAPI entry for the new endpoint so it shows up in
/v1 (swagger UI) and the SDK codegen pipeline.

Documents the four query params (user_id required; limit, offset,
max_per_artist optional with min/max bounds matching the handler's
validate tags) and points the 200 response at the existing
"tracks" component schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server-side replacement for the client-side blend in apps'
useForYouFeed.ts. Four candidate streams interleaved with a fixed
10-slot pattern,

  pos: [R R R F R T R F U R]

giving 60% recommended (50% baseline + 10% filler when other sources
thin out), 20% following originals, 10% trending, 10% underground.
When a slot's preferred source is exhausted, falls through in priority
order recommended → following → trending → underground.

Each source caps at 200 candidates; the union is deduped by track_id
and the caller's already-saved tracks are filtered at the SQL level
(client filtered them client-side; filtering early avoids burning
candidates on already-saved tracks). Pagination (limit/offset)
applies to the composed list.

Source SQL mirrors the underlying single-source endpoints:
- recommended: v1_users_recommended_tracks.go (top tracks from the
  user's top-played genres, excluding played) — switched to
  score-DESC ordering for stable pagination
- following:   v1_users_feed.go w/ filter=original
- trending:    v1_tracks_trending.go (week)
- underground: v1_tracks_trending_underground.go (week, sub-1500
  follower & following artists)

Liveness, gating, and unlisted/deleted filters mirror those files
and v1_events_remix_contests.go.

Path-param userId (the requesting user) follows the same pattern as
/v1/users/:userId/recommended-tracks and /v1/users/:userId/feed.

Consumed by apps' useForYouFeed.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dylanjeffers added a commit that referenced this pull request May 8, 2026
The path-param convention matches every other personalized track
endpoint in the API (/v1/users/{id}/feed, /v1/users/{id}/recommended-tracks,
etc.), is what apps#14237's description calls out as the consumer-facing
path, and lets requireUserIdMiddleware do the hash-id validation that
the handler had been duplicating.

What moved:
- Route: g.Get("/feed/for-you", ...) → g.Get("/users/:userId/feed/for-you", ...)
  registered under the same requireUserIdMiddleware group as the rest of
  /users/:userId/...
- File:  v1_feed_for_you.go → v1_users_feed_for_you.go
- Func:  v1FeedForYou → v1UsersFeedForYou
- Param struct: GetFeedForYouParams → GetUsersFeedForYouParams
- Swagger entry moved next to /users/{id}/feed and reshaped to take
  `id` as a path parameter; query `user_id` is now optional and used
  only for the caller's viewer-relative track fields, mirroring the
  rest of the user-scoped endpoints.

What stayed (intentionally — this is what the PR is for):
- All four candidate sources: in_network, trending, underground,
  similar (1-hop CF on saves graph)
- Cross-source ranking: 48h half-life × engagement × social_boost ×
  source_weight, all the same coefficients
- Per-artist row_number cap + Go-side greedy 5-track lookahead
- Filter set: live tracks, owner liveness, access_authorities,
  exclude already-saved, exclude path-user's own uploads
- Pool size, max_per_artist param, default limit of 25

Handler signature change: the path id is now the user being
personalized for (drives the SQL @userid), and the optional
?user_id= caller is myId for track shape — the same split every
other /v1/users/{id}/... endpoint uses.

Tests: same fixtures and assertions, URLs rewritten to
/v1/users/{id}/feed/for-you. The "requires user id" test now
asserts an invalid hash id returns 400 (from the middleware)
rather than a missing query param.

Supersedes #797.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dylanjeffers
Copy link
Copy Markdown
Contributor Author

Superseded by #787 — that PR's branch (feed/for-you-algorithm) now serves the Twitter-style ranker on /v1/users/{id}/feed/for-you. It keeps the better algorithm (4-source candidate retrieval including the 1-hop saves CF, cross-source recency × engagement × social_boost ranking, per-artist diversity pass) on the user-scoped path apps#14237 expects. Closing this rather than the other way around so the more sophisticated ranker is what ships.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant