feat(discover): show why each item is suggested
Each suggestion now carries the followed artists (seeds) that surfaced it, from DiscoverySeedContribution joined to the current watched roster (an unfollowed seed drops out), highest-scoring seed first. /discover renders "Similar to Arctic Monkeys, Coldplay +2" under each artist and album row — both kinds key off the candidate's artistMbid, so an album inherits its artist's seeds. web 187 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,30 @@ export async function GET() {
|
||||
where: { status: "pending" },
|
||||
orderBy: [{ score: "desc" }, { createdAt: "asc" }],
|
||||
});
|
||||
|
||||
// "Why suggested": the followed artists (seeds) that surfaced each candidate. A suggestion's
|
||||
// candidate is its artistMbid (for both artist and album kinds — the album's artist is what
|
||||
// was found similar). Join the per-seed contributions to the current watched roster so an
|
||||
// unfollowed seed drops out. Highest-scoring seed first.
|
||||
const candidateMbids = [...new Set(rows.map((r) => r.artistMbid))];
|
||||
const contribs = candidateMbids.length
|
||||
? await prisma.discoverySeedContribution.findMany({
|
||||
where: { candidateMbid: { in: candidateMbids } },
|
||||
orderBy: { score: "desc" },
|
||||
})
|
||||
: [];
|
||||
const seedNames = new Map(
|
||||
(await prisma.watchedArtist.findMany({ select: { mbid: true, name: true } })).map((w) => [w.mbid, w.name]),
|
||||
);
|
||||
const seedsByCandidate = new Map<string, string[]>();
|
||||
for (const c of contribs) {
|
||||
const name = seedNames.get(c.seedMbid);
|
||||
if (!name) continue; // seed no longer followed
|
||||
const list = seedsByCandidate.get(c.candidateMbid) ?? [];
|
||||
if (!list.includes(name)) list.push(name);
|
||||
seedsByCandidate.set(c.candidateMbid, list);
|
||||
}
|
||||
|
||||
const map = (r: (typeof rows)[number]) => ({
|
||||
id: r.id,
|
||||
artistMbid: r.artistMbid,
|
||||
@@ -17,6 +41,7 @@ export async function GET() {
|
||||
score: r.score,
|
||||
seedCount: r.seedCount,
|
||||
sources: r.sources,
|
||||
seeds: seedsByCandidate.get(r.artistMbid) ?? [],
|
||||
});
|
||||
return Response.json({
|
||||
artists: rows.filter((r) => r.kind === "artist").map(map),
|
||||
|
||||
Reference in New Issue
Block a user