fix(vibe): keep playback advancing with full plans
This commit is contained in:
@@ -55,6 +55,7 @@ describe('DbService v2 methods', () => {
|
|||||||
expect(clientQuery.mock.calls[4][0]).toContain('INSERT INTO evidence');
|
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('exploration_coefficient');
|
||||||
expect(clientQuery.mock.calls[8][0]).toContain('ELSE goals END');
|
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[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);
|
expect(clientQuery.mock.calls.filter(([sql]) => String(sql).includes('INSERT INTO evidence'))).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1664,12 +1664,12 @@ export class DbService {
|
|||||||
|
|
||||||
const profile = await client.query(
|
const profile = await client.query(
|
||||||
`UPDATE vibe_session_profiles
|
`UPDATE vibe_session_profiles
|
||||||
SET exploration_coefficient = GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0 ELSE $3 END)),
|
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 ELSE $3 END)) * 0.65))),
|
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
|
goals = CASE
|
||||||
WHEN goals->>'type' = 'familiar' AND $4 AND $5
|
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))))
|
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))))
|
THEN jsonb_set(goals, '{progress}', to_jsonb(LEAST(COALESCE((goals->>'progress')::int, 0) + 1, COALESCE((goals->>'target')::int, 1))))
|
||||||
ELSE goals END,
|
ELSE goals END,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export interface GeneratorContext {
|
|||||||
export type Generator = (db: DbService, ctx: GeneratorContext) => Promise<Candidate[]>;
|
export type Generator = (db: DbService, ctx: GeneratorContext) => Promise<Candidate[]>;
|
||||||
|
|
||||||
const OBJECTIVE_USER = '00000000-0000-0000-0000-000000000000';
|
const OBJECTIVE_USER = '00000000-0000-0000-0000-000000000000';
|
||||||
|
const FALLBACK_CANDIDATE_LIMIT = 60;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 1. COMFORT — Top artists by longterm affinity > 0.5
|
// 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<Candidate[]> {
|
||||||
|
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)
|
// All generators, ordered by priority (comfort first, experimental last)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -548,4 +576,5 @@ export const ALL_GENERATORS: Generator[] = [
|
|||||||
noveltyGenerator,
|
noveltyGenerator,
|
||||||
contextualGenerator,
|
contextualGenerator,
|
||||||
experimentalGenerator,
|
experimentalGenerator,
|
||||||
|
libraryFallbackGenerator,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -199,8 +199,8 @@ describe('generators', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('ALL_GENERATORS', () => {
|
describe('ALL_GENERATORS', () => {
|
||||||
it('contains 8 generators', () => {
|
it('contains 9 generators', () => {
|
||||||
expect(ALL_GENERATORS).toHaveLength(8);
|
expect(ALL_GENERATORS).toHaveLength(9);
|
||||||
ALL_GENERATORS.forEach(g => expect(typeof g).toBe('function'));
|
ALL_GENERATORS.forEach(g => expect(typeof g).toBe('function'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user