import { describe, expect, it } from 'vitest'; import type { TFunction } from 'i18next'; import { deriveMoodScores, formatQueueBpmTech, formatQueueMoodLabels, parseTrackEnrichmentFacts, resolveQueueBpm, resolveDisplayBpm, topMoodLabelIds, } from './trackEnrichment'; import { topDistinctOximediaMoodTagIds, topOximediaMoodTagIds } from '../../config/moodGroups'; const t = ((key: string, opts?: Record) => { if (key === 'queue.bpm') return `${opts?.bpm} BPM`; if (key === 'queue.moods.calm') return 'Calm'; if (key === 'queue.moods.peaceful') return 'Peaceful'; return key; }) as TFunction; describe('parseTrackEnrichmentFacts', () => { it('does not surface oximedia mood labels in UI while detector is disabled', () => { const parsed = parseTrackEnrichmentFacts( [ { serverId: 's1', trackId: 't1', factKind: 'moods', sourceKind: 'analysis', sourceId: 'oximedia-60s-center', valueText: '{"calm":0.6,"peaceful":0.4}', confidence: 0.9, fetchedAt: 1, }, { serverId: 's1', trackId: 't1', factKind: 'valence', sourceKind: 'analysis', sourceId: 'oximedia-60s-center', valueReal: 0.55, confidence: 0.9, fetchedAt: 1, }, { serverId: 's1', trackId: 't1', factKind: 'arousal', sourceKind: 'analysis', sourceId: 'oximedia-60s-center', valueReal: 0.42, confidence: 0.9, fetchedAt: 1, }, ], null, ); expect(parsed.moodLabels).toEqual([]); }); it('reads analysis bpm fact with highest confidence', () => { const parsed = parseTrackEnrichmentFacts( [ { serverId: 's1', trackId: 't1', factKind: 'bpm', sourceKind: 'analysis', sourceId: 'oximedia-60s-center', valueInt: 128, confidence: 0.9, fetchedAt: 1, }, { serverId: 's1', trackId: 't1', factKind: 'bpm', sourceKind: 'analysis', sourceId: 'other', valueInt: 110, confidence: 0.5, fetchedAt: 1, }, ], 120, ); expect(parsed.measuredBpm).toBe(128); expect(parsed.serverBpm).toBe(120); expect(resolveQueueBpm(parsed)).toBe(128); }); }); describe('resolveQueueBpm', () => { it('prefers measured analysis bpm over tag', () => { expect(resolveQueueBpm({ serverBpm: 120, measuredBpm: 128, moodLabels: [] })).toBe(128); }); it('falls back to tag bpm when no analysis fact', () => { expect(resolveQueueBpm({ serverBpm: 120, measuredBpm: null, moodLabels: [] })).toBe(120); }); it('falls back to measured when tag bpm missing', () => { expect(resolveQueueBpm({ serverBpm: null, measuredBpm: 128, moodLabels: [] })).toBe(128); }); }); describe('resolveDisplayBpm', () => { it('ignores zero tag bpm and uses measured', () => { expect(resolveDisplayBpm(0, 132)).toBe(132); }); }); describe('formatters', () => { it('formats bpm for tech row', () => { expect(formatQueueBpmTech({ serverBpm: 120, measuredBpm: 128, moodLabels: [] }, t)).toBe('128 BPM'); }); it('localizes mood labels without weights', () => { expect(formatQueueMoodLabels(['calm', 'peaceful'], t)).toBe('Calm ยท Peaceful'); }); }); describe('topMoodLabelIds', () => { it('returns at most two distinct cluster labels from valence/arousal', () => { const labels = topMoodLabelIds(0.4, 0.75); expect(labels.includes('happy') && labels.includes('excited')).toBe(false); expect(labels.length).toBeLessThanOrEqual(2); }); }); describe('distinct mood tag picking', () => { it('never keeps both happy and excited from raw scores', () => { expect(topDistinctOximediaMoodTagIds({ calm: 0.52, happy: 0.9, excited: 0.5 })).toEqual(['happy', 'calm']); }); it('sorts by score descending with id tie-break', () => { expect(topOximediaMoodTagIds({ calm: 0.2, happy: 0.9, excited: 0.5 })).toEqual([ 'happy', 'excited', 'calm', ]); }); }); describe('deriveMoodScores', () => { it('delegates to soft valence/arousal scoring', () => { const scores = deriveMoodScores(0.55, 0.42); expect(topDistinctOximediaMoodTagIds(scores, 2).some(id => id === 'calm' || id === 'peaceful')).toBe(true); }); });