fix: soften vibe artist exclusions
This commit is contained in:
@@ -13,8 +13,10 @@ interface ActivePlan {
|
||||
servedTrackIds?: string[];
|
||||
/** Explicit feedback targets (skip, dislike, completion, promotion). */
|
||||
excludedTrackIds?: string[];
|
||||
/** Main artists served or explicitly rejected in this session. */
|
||||
excludedArtistIds?: string[];
|
||||
/** Main artists already heard in this Vibe; they get a mild rank penalty. */
|
||||
servedArtistIds?: string[];
|
||||
/** Artists explicitly skipped/disliked; they receive the stronger penalty. */
|
||||
downrankedArtistIds?: string[];
|
||||
}
|
||||
|
||||
const PLAN_TTL_SEC = 2 * 3600;
|
||||
@@ -26,9 +28,9 @@ function planKey(userId: string): string {
|
||||
return `v2:plan:${userId}`;
|
||||
}
|
||||
|
||||
function appendUniqueTrackId(ids: string[] | undefined, trackId: string): string[] {
|
||||
function appendUniqueId(ids: string[] | undefined, id: string): string[] {
|
||||
const next = ids ? [...ids] : [];
|
||||
if (!next.includes(trackId)) next.push(trackId);
|
||||
if (!next.includes(id)) next.push(id);
|
||||
return next.length > MAX_SESSION_EXCLUSIONS
|
||||
? next.slice(next.length - MAX_SESSION_EXCLUSIONS)
|
||||
: next;
|
||||
@@ -41,8 +43,11 @@ function sessionExclusions(active: ActivePlan): string[] {
|
||||
])];
|
||||
}
|
||||
|
||||
function sessionArtistExclusions(active: ActivePlan): string[] {
|
||||
return [...new Set(active.excludedArtistIds ?? [])];
|
||||
function sessionArtistPenalties(active: ActivePlan): Map<string, number> {
|
||||
const penalties = new Map<string, number>();
|
||||
for (const artistId of active.servedArtistIds ?? []) penalties.set(artistId, 0.2);
|
||||
for (const artistId of active.downrankedArtistIds ?? []) penalties.set(artistId, 0.75);
|
||||
return penalties;
|
||||
}
|
||||
|
||||
export default async function v2Routes(fastify: FastifyInstance, options: { dbService: DbService; sessionDirector: SessionDirector }) {
|
||||
@@ -148,7 +153,7 @@ export default async function v2Routes(fastify: FastifyInstance, options: { dbSe
|
||||
}
|
||||
|
||||
const next = active.plan.shift()!;
|
||||
active.servedTrackIds = appendUniqueTrackId(active.servedTrackIds, next.trackId);
|
||||
active.servedTrackIds = appendUniqueId(active.servedTrackIds, next.trackId);
|
||||
const artistResult = await dbService.pgClient.query<{ artist_id: string }>(
|
||||
`SELECT artist_id FROM track_artists_v2
|
||||
WHERE track_id = $1 AND role = 'main'
|
||||
@@ -156,7 +161,7 @@ export default async function v2Routes(fastify: FastifyInstance, options: { dbSe
|
||||
[next.trackId]
|
||||
);
|
||||
if (artistResult.rows[0]?.artist_id) {
|
||||
active.excludedArtistIds = appendUniqueTrackId(active.excludedArtistIds, artistResult.rows[0].artist_id);
|
||||
active.servedArtistIds = appendUniqueId(active.servedArtistIds, artistResult.rows[0].artist_id);
|
||||
}
|
||||
// Enrich with track details
|
||||
const track = await dbService.getTrackById(next.trackId);
|
||||
@@ -169,7 +174,7 @@ export default async function v2Routes(fastify: FastifyInstance, options: { dbSe
|
||||
active.plan,
|
||||
[next.trackId],
|
||||
active.seedTrackId ?? undefined,
|
||||
{ excludedTrackIds: sessionExclusions(active), excludedArtistIds: sessionArtistExclusions(active) }
|
||||
{ excludedTrackIds: sessionExclusions(active), artistPenalties: sessionArtistPenalties(active) }
|
||||
);
|
||||
active.plan = refill;
|
||||
}
|
||||
@@ -233,15 +238,17 @@ export default async function v2Routes(fastify: FastifyInstance, options: { dbSe
|
||||
if (!sessionId || active.sessionId !== sessionId) return 0;
|
||||
// Feedback may race /next or arrive after a client-side prefetch. In all
|
||||
// cases its track becomes ineligible for the rest of this session.
|
||||
active.excludedTrackIds = appendUniqueTrackId(active.excludedTrackIds, trackId);
|
||||
const artistResult = await dbService.pgClient.query<{ artist_id: string }>(
|
||||
`SELECT artist_id FROM track_artists_v2
|
||||
WHERE track_id = $1 AND role = 'main'
|
||||
ORDER BY confidence DESC NULLS LAST LIMIT 1`,
|
||||
[trackId]
|
||||
);
|
||||
if (artistResult.rows[0]?.artist_id) {
|
||||
active.excludedArtistIds = appendUniqueTrackId(active.excludedArtistIds, artistResult.rows[0].artist_id);
|
||||
active.excludedTrackIds = appendUniqueId(active.excludedTrackIds, trackId);
|
||||
if (action === 'skipped' || action === 'disliked') {
|
||||
const artistResult = await dbService.pgClient.query<{ artist_id: string }>(
|
||||
`SELECT artist_id FROM track_artists_v2
|
||||
WHERE track_id = $1 AND role = 'main'
|
||||
ORDER BY confidence DESC NULLS LAST LIMIT 1`,
|
||||
[trackId]
|
||||
);
|
||||
if (artistResult.rows[0]?.artist_id) {
|
||||
active.downrankedArtistIds = appendUniqueId(active.downrankedArtistIds, artistResult.rows[0].artist_id);
|
||||
}
|
||||
}
|
||||
const sessionTrackIds = sessionExclusions(active);
|
||||
const refill = await director.replan(
|
||||
@@ -250,7 +257,7 @@ export default async function v2Routes(fastify: FastifyInstance, options: { dbSe
|
||||
active.plan,
|
||||
[trackId],
|
||||
active.seedTrackId ?? undefined,
|
||||
{ excludedTrackIds: sessionTrackIds, excludedArtistIds: sessionArtistExclusions(active) }
|
||||
{ excludedTrackIds: sessionTrackIds, artistPenalties: sessionArtistPenalties(active) }
|
||||
);
|
||||
active.plan = refill;
|
||||
await setActivePlan(userId, active);
|
||||
|
||||
Reference in New Issue
Block a user