fix: soften vibe artist exclusions
Typecheck / typecheck (backend) (push) Has been cancelled
Typecheck / typecheck (workers) (push) Has been cancelled

This commit is contained in:
kami
2026-08-01 16:27:55 +04:00
parent 4c48d11e9d
commit e62b7e8d10
3 changed files with 68 additions and 29 deletions
+26 -19
View File
@@ -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);