feat(api): add Discover Weekly mix endpoint - #1025
Merged
Merged
Conversation
GET /v1/users/:id/discover-weekly returns a fixed-size, taste-matched track mix: tracks the listener hasn't heard, weighted toward artists they don't already follow. Deliberately not a stored playlist. Audius playlists are on-chain entities, so minting one per user per week isn't viable; instead the query is deterministic given (user_id, iso_year, iso_week) and a hashtextextended seed on that tuple rotates the mix weekly without random(). Cached 6h -- entries are immutable for their key, so the TTL only bounds memory. Reuses the For You feed's genre-affinity and engagement terms but inverts the in-network weight (followed 0.70 vs unfollowed 1.25), hard-excludes played/saved rather than soft-penalizing, drops the recency half-life, and caps one track per artist. The product difference from /feed/for-you is artifact-vs-feed, not ranking quality. Known limitation: the played-exclusion has no upper time bound, so the mix can shrink mid-week as the listener plays through it once the cache expires. Fixing that properly needs a stored track list per (user, year, week), which also gives the mix a URL. Tracked as follow-up. Not yet profiled against production-scale data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dylanjeffers
added a commit
that referenced
this pull request
Aug 24, 2026
`/users/:id/discover-weekly` (shipped in #1025) returns an **empty mix for every user** in production. Verified against prod across six accounts — all returned `{"data": []}`. ## Root cause Two bugs compounded. `track_trending_scores` holds two populations: | rows | read by | median track age | |---|---|---| | genre-carrying | `/tracks/trending`, `/tracks/trending/underground` | **~4 days** (max 12) | | null/empty genre | this endpoint, and `/feed/for-you` | **~5–6 years** | The candidate CTEs matched only the null-genre rows, so every candidate was ancient — then the 365-day age cutoff in the filter stage dropped all of them. Empty result, every time. Measured on prod: the 18 out-of-network tracks in deadmau5's for-you feed ranged from **1822 to 2175 days old**, while `/tracks/trending` top-100 ranged 0–10 days. ## Fix Drop the genre predicate from both candidate CTEs, so they read the live rows the same way the trending endpoints do. The age cutoff stays but is no longer load-bearing. ## Why no test caught it Every existing test seeded score rows without a genre, so the fixtures only ever exercised the stale-population path. Adds one that seeds a genre-carrying row — the shape that actually reaches the query in production. ## Related, not fixed here `/users/:id/feed/for-you` uses the same `genre IS NULL OR genre = ''` predicate on its trending and underground candidate sources ([lines 327 and 346](https://github.com/AudiusProject/api/blob/main/api/v1_users_feed_for_you.go#L327)). It doesn't return empty because it has in-network and playlist sources to fall back on, but its trending candidates are very likely the same 5–6-year-old tracks — being ranked by a scorer with a 48-hour recency half-life. Worth a separate look. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dylanjeffers
added a commit
to AudiusProject/apps
that referenced
this pull request
Aug 24, 2026
Wires `GET /users/:id/discover-weekly` into the Explore page on web and native, rendered as a collection card so the mix reads like a playlist. Depends on [api#1025](AudiusProject/api#1025) and [api#1026](AudiusProject/api#1026) — both merged and live in production. ## What's here - `useDiscoverWeekly` — a plain `useQuery`, not infinite. The mix is a fixed-size artifact, not a lineup you scroll; there is no page 2. - `sdk.users.getDiscoverWeekly` — hand-written pending the next SDK regen, same as `getSuggestedFollows` in #14562, since `npm run gen` pulls the spec from a running node. - Web section (desktop + mobile web) and a parallel native section, since `packages/mobile` composes its own Explore screen. ## Three decisions worth reviewing **The artwork is a checked-in asset.** The mix has no `playlist_id` to hang cover art on — Audius playlists are on-chain entities and the mix is computed per request. Styled to sit alongside the Hot & New playlist art. **Clicking plays instead of navigating.** No permalink to navigate to, so the card queues all 30 starting at the top. Closest thing to playlist behavior without a route. **No section heading or Carousel.** One card, so the scroll affordance is dead weight and a heading would repeat the card's own title. ## Known gaps - **The card has playlist affordances without playlist substance** — can't be favorited, reposted, shared, or linked to. Closing that needs the stored track-list per `(user, year, week)`, which would also fix the mid-week drift noted in api#1025. - **Not visually verified in the running app** at time of opening — the section is signed-in-only. ## Verification `tsc` and eslint clean across web, mobile, and sdk. The one `common` error (`getDiscoverWeekly` not on `UsersApi`) is the known SDK-dist parity issue — the built `dist/index.d.ts` types `sdk.users` as the generated class, so the already-merged `useSuggestedFollows` produces an identical error. Resolves on the next regen. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
GET /v1/users/:id/discover-weekly— a fixed-size, taste-matched track mix: tracks the listener hasn't heard, weighted toward artists they don't already follow.Why it isn't a stored playlist
Audius playlists are on-chain entities, so minting one per user per week isn't viable. Instead the query is deterministic given
(user_id, iso_year, iso_week), and ahashtextextendedseed on that tuple rotates the mix weekly withoutrandom(). Cached 6h; entries are immutable for their key, so the TTL only bounds memory.Why it isn't the For You feed
Reuses
/feed/for-you's genre-affinity and engagement terms, but:The product difference is artifact-vs-feed, not ranking quality.
Known limitations
(user, year, week)— which also gives the mix a URL. Follow-up.EXPLAIN ANALYZEagainst a replica with a heavy listener before it takes real traffic.Tests
5 new tests covering every filter, the one-per-artist cap, followed-artist demotion, cold start, and week-to-week rotation. Full suite green.
🤖 Generated with Claude Code