From 1f44af589329731eefbf8d157b7cda86becd91ee Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 3 Aug 2026 14:12:22 +0400 Subject: [PATCH] fix(vibe): keep playback advancing with full plans --- backend/src/services/db.service.test.ts | 1 + backend/src/services/db.service.ts | 6 ++--- backend/src/services/generators.service.ts | 29 ++++++++++++++++++++++ backend/src/services/generators.test.ts | 4 +-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/backend/src/services/db.service.test.ts b/backend/src/services/db.service.test.ts index 2052cc9..174227d 100644 --- a/backend/src/services/db.service.test.ts +++ b/backend/src/services/db.service.test.ts @@ -55,6 +55,7 @@ describe('DbService v2 methods', () => { expect(clientQuery.mock.calls[4][0]).toContain('INSERT INTO evidence'); expect(clientQuery.mock.calls[8][0]).toContain('exploration_coefficient'); expect(clientQuery.mock.calls[8][0]).toContain('ELSE goals END'); + expect(clientQuery.mock.calls[8][0]).toContain('ELSE $3::real END'); expect(clientQuery.mock.calls[9][1][4]).toBe(JSON.stringify({ type: 'familiar', target: 1, progress: 1 })); expect(clientQuery.mock.calls.filter(([sql]) => String(sql).includes('INSERT INTO evidence'))).toHaveLength(1); }); diff --git a/backend/src/services/db.service.ts b/backend/src/services/db.service.ts index 11f66e0..d76e3df 100644 --- a/backend/src/services/db.service.ts +++ b/backend/src/services/db.service.ts @@ -1664,12 +1664,12 @@ export class DbService { const profile = await client.query( `UPDATE vibe_session_profiles - SET exploration_coefficient = GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0 ELSE $3 END)), - discovery_radius = GREATEST(0.15, LEAST(0.9, 0.2 + (GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0 ELSE $3 END)) * 0.65))), + SET exploration_coefficient = GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0::real ELSE $3::real END)), + discovery_radius = GREATEST(0.15, LEAST(0.9, 0.2 + (GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0::real ELSE $3::real END)) * 0.65))), goals = CASE WHEN goals->>'type' = 'familiar' AND $4 AND $5 THEN jsonb_set(goals, '{progress}', to_jsonb(LEAST(COALESCE((goals->>'progress')::int, 0) + 1, COALESCE((goals->>'target')::int, 1)))) - WHEN goals->>'type' IN ('discovery', 'surprise', 'artist_introduction') AND NOT $4 AND $3 > 0 + WHEN goals->>'type' IN ('discovery', 'surprise', 'artist_introduction') AND NOT $4 AND $3::real > 0 THEN jsonb_set(goals, '{progress}', to_jsonb(LEAST(COALESCE((goals->>'progress')::int, 0) + 1, COALESCE((goals->>'target')::int, 1)))) ELSE goals END, updated_at = NOW() diff --git a/backend/src/services/generators.service.ts b/backend/src/services/generators.service.ts index 6b937fd..6e18b2e 100644 --- a/backend/src/services/generators.service.ts +++ b/backend/src/services/generators.service.ts @@ -60,6 +60,7 @@ export interface GeneratorContext { export type Generator = (db: DbService, ctx: GeneratorContext) => Promise; const OBJECTIVE_USER = '00000000-0000-0000-0000-000000000000'; +const FALLBACK_CANDIDATE_LIMIT = 60; // --------------------------------------------------------------------------- // 1. COMFORT — Top artists by longterm affinity > 0.5 @@ -536,6 +537,33 @@ async function contextualGenerator(db: DbService, ctx: GeneratorContext): Promis }); } +// --------------------------------------------------------------------------- +// 9. LIBRARY FALLBACK — keeps a Vibe usable when the metadata graph is sparse +// --------------------------------------------------------------------------- +async function libraryFallbackGenerator(db: DbService, ctx: GeneratorContext): Promise { + const res = await db.pgClient.query( + `SELECT t.id + FROM tracks t + WHERE t.state = 'LIBRARY' + AND NOT (t.id = ANY($1::uuid[])) + ORDER BY RANDOM() + LIMIT $2`, + [ctx.recentExclusions, FALLBACK_CANDIDATE_LIMIT], + ); + + return (res.rows as { id: string }[]).map(row => ({ + trackId: row.id, + generatorId: 'library-fallback', + relevance: 0.05, + explanation: [{ + subjectType: 'track', subjectId: row.id, + predicate: 'library_fallback', + objectType: 'track', objectId: row.id, + fusedValue: 0.05, + }], + })); +} + // --------------------------------------------------------------------------- // All generators, ordered by priority (comfort first, experimental last) // --------------------------------------------------------------------------- @@ -548,4 +576,5 @@ export const ALL_GENERATORS: Generator[] = [ noveltyGenerator, contextualGenerator, experimentalGenerator, + libraryFallbackGenerator, ]; diff --git a/backend/src/services/generators.test.ts b/backend/src/services/generators.test.ts index e23e733..3536d69 100644 --- a/backend/src/services/generators.test.ts +++ b/backend/src/services/generators.test.ts @@ -199,8 +199,8 @@ describe('generators', () => { }); describe('ALL_GENERATORS', () => { - it('contains 8 generators', () => { - expect(ALL_GENERATORS).toHaveLength(8); + it('contains 9 generators', () => { + expect(ALL_GENERATORS).toHaveLength(9); ALL_GENERATORS.forEach(g => expect(typeof g).toBe('function')); }); });