Skip to content

feat(bot): Spotify audio batch features, artist popularity, album cohesion (phases B/D/E) - #579

Merged
LucasSantana-Dev merged 6 commits into
mainfrom
feat/autoplay-bde
Apr 13, 2026
Merged

LucasSantana-Dev merged 6 commits into
mainfrom
feat/autoplay-bde

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Apr 12, 2026 •

Copy link
Copy Markdown
Owner

Depends on #577, #578

Builds on the smart signals + decay/mood PRs. The full chain: #577 → #578 → this PR.

Phase B — Spotify Audio Feature Batch Matching

After candidate selection, fetch energy/valence/danceability for all candidates that have Spotify URLs — one API call for up to 100 tracks.

Scoring adjustments vs current track's audio profile:

Delta Adjustment
energy < 0.15 AND valence < 0.20 +0.15 strong mood match
energy < 0.30 OR valence < 0.35 +0.07 partial match
energy > 0.60 −0.10 strong mood mismatch

Only runs when user has a linked Spotify account. Gracefully skips if no token.

Phase D — Artist Popularity Weighting

After candidate selection, fetch Spotify popularity (0–100) for the top 3 candidates. Module-level cache — popularity is stable, no need to re-fetch.

Condition Adjustment
popular mode + artist popularity ≥ 70 +0.12 mainstream boost
discover mode + artist popularity ≤ 40 +0.12 emerging artist boost

Only fires for popular and discover modes, not similar.

Phase E — Album Cohesion

In calculateRecommendationScore, same-artist candidates with title token similarity > 0.4 get +0.12 "album match" boost. Picks up tracks from the same album or era without needing an explicit album metadata field.

Tests

18 new tests in spotifyApi.spec.ts covering getBatchAudioFeatures and getArtistPopularity including empty input, partial results, caching, and all error paths.

Summary by CodeRabbit

  • New Features

    • Autoplay now uses Spotify artist popularity and batch track audio features to improve recommendation relevance and diversity.
    • Group/listening sessions weight recommendations by each member's contribution history for fairer allocations.
    • Recommendation scoring updated: audio-feature compatibility, artist-popularity boosts, and stricter same-artist handling improve selection quality.
  • Tests

    • Added and expanded unit tests covering audio-feature batching, artist-popularity lookup, weighted seed blending, and contribution-weight calculations.

@vercel

vercel Bot commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Apr 13, 2026 3:53am

@coderabbitai

coderabbitai Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 11 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 25 minutes and 11 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7abb6000-98c9-4465-90f5-541a55fe5040

📥 Commits

Reviewing files that changed from the base of the PR and between 4cfe14b and 32c4519.

📒 Files selected for processing (1)
  • packages/bot/src/utils/music/queueManipulation.spec.ts
📝 Walkthrough

Walkthrough

Adds Spotify batch audio-feature fetching and artist-popularity lookup (with caching), integrates these into autoplay candidate enrichment and scoring, and extends Last.fm seed blending to accept per-user weights with a new VC contribution-weight builder.

Changes

Cohort / File(s) Summary
Spotify API
packages/bot/src/spotify/spotifyApi.ts, packages/bot/src/spotify/spotifyApi.spec.ts
Added getBatchAudioFeatures(accessToken, spotifyIds): Promise<Map<string, SpotifyAudioFeatures>> and `getArtistPopularity(accessToken, artistName): Promise<number
Last.fm weighted seed blending
packages/bot/src/utils/music/autoplay/lastFmSeeds.ts, packages/bot/src/utils/music/autoplay/lastFmSeeds.spec.ts
consumeBlendedSeedSlice(userIds, count, weights?) now accepts optional per-user weights Map and allocates per-user slice sizes proportionally (defaults and clamping applied). Added test validating proportional allocation.
Queue manipulation & enrichment
packages/bot/src/utils/music/queueManipulation.ts, packages/bot/src/utils/music/queueManipulation.spec.ts
Threaded VC-member weighting into Last.fm seed collection (calls buildVcContributionWeights and passes weights to consumeBlendedSeedSlice). Added buildVcContributionWeights(historyTracks, vcMemberIds): Map<string, number> export. Introduced enrichWithAudioFeatures(...) that batch-fetches Spotify audio features, adjusts candidate scores by energy/valence deltas, and applies mode-specific artist-popularity boosts; updated recommendation scoring to add artist-match penalty and title-token album bonus. Tests updated/mocks extended and call signatures adjusted.

Sequence Diagram

sequenceDiagram
    participant Client
    participant QueueMgr as Queue Manager
    participant LastFmAPI as Last.fm API
    participant SpotifyAPI as Spotify API
    participant ArtistCache as Artist Pop Cache

    Client->>QueueMgr: request autoplay/enrich
    QueueMgr->>QueueMgr: buildVcContributionWeights(history, vcMembers)
    QueueMgr->>LastFmAPI: consumeBlendedSeedSlice(userIds, count, weights)
    LastFmAPI-->>QueueMgr: seed candidates
    QueueMgr->>QueueMgr: selectDiverseCandidates()

    QueueMgr->>SpotifyAPI: getAudioFeatures(currentTrackId)
    SpotifyAPI-->>QueueMgr: current track features

    QueueMgr->>SpotifyAPI: getBatchAudioFeatures(candidateIds)
    SpotifyAPI-->>QueueMgr: candidate audio features (Map)
    QueueMgr->>QueueMgr: enrichWithAudioFeatures(candidates)

    alt popularity boost needed
        QueueMgr->>ArtistCache: lookup artistName
        alt cache miss
            QueueMgr->>SpotifyAPI: getArtistPopularity(artistName)
            SpotifyAPI-->>ArtistCache: store popularity (number|null)
        end
        ArtistCache-->>QueueMgr: popularity result
        QueueMgr->>QueueMgr: apply popularity bonus and re-sort
    end

    QueueMgr-->>Client: return enriched, scored candidates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(bot): Spotify audio batch features, artist popularity, album cohesion (phases B/D/E)' accurately and specifically describes the main changes: batch Spotify audio features, artist popularity scoring, and album cohesion logic across the three implementation phases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/autoplay-bde

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…esion scoring

phase b — audio feature batch matching:
- getBatchAudioFeatures(): fetches up to 100 track features in one Spotify API call
- enrichWithAudioFeatures(): runs after selectDiverseCandidates; extracts Spotify IDs from
  track URLs, batch-fetches features, applies energy+valence delta scoring:
  energyDelta<0.15 && valenceDelta<0.20 → +0.15, partial match → +0.07, mismatch >0.6 → -0.10

phase d — artist popularity weighting:
- getArtistPopularity(): searches Spotify, returns 0-100 popularity with module-level cache
- top 3 candidates enriched post-selection when mode=popular or discover:
  popular mode + popularity≥70 → +0.12; discover mode + popularity≤40 → +0.12

phase e — album cohesion:
- in calculateRecommendationScore: same-artist candidates with title token similarity >0.4
  get +0.12 album match boost (same album/era without needing album metadata field)

18 new tests in spotifyApi.spec.ts covering all new functions and error paths
- buildVcContributionWeights(): counts manual tracks requested per vc member,
  normalises so total weight = member count, baseline 1 for zero-contribution members
- consumeBlendedSeedSlice(): accepts optional weights map and allocates proportional
  seed slices (min 1 per user), falls back to equal distribution when no weights
- heavy listener in a 3-person vc now gets proportionally more seed slots than
  someone who joined but hasn't requested anything yet
- 4 new tests: equal weights, heavy listener, zero-contrib baseline, weighted blend
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/bot/src/spotify/spotifyApi.spec.ts`:
- Line 1: Remove the leftover merge conflict markers in the spotifyApi.spec.ts
test file: search for and delete any occurrences of "<<<<<<<", "=======", and
">>>>>>>", resolve the conflicting blocks by choosing or merging the correct
test code (ensure the resulting describe/it blocks and imports are syntactically
valid), and run the tests to confirm the spec parses; specifically inspect the
top of the file and the region around the former marker at the end of the file
to reconstruct a single coherent test definition.
- Around line 9-10: The test calls jest.resetModules() after imports so the
module-scoped artistPopularityCache created in spotifyApi.ts still persists
across tests; to fix this, ensure the cache is cleared between tests by either
(A) resetting module state before each test: call jest.resetModules() in a
beforeEach and re-import getArtistPopularity (so spotifyApi.ts reinitializes
artistPopularityCache), or (B) import the module once and explicitly clear the
Map in a beforeEach by accessing the cached symbol (artistPopularityCache) via
jest.mocked or by exposing a test-only clearArtistPopularityCache helper in
spotifyApi.ts and calling that in beforeEach; update tests to use one of these
approaches so getArtistPopularity runs with a fresh cache each test.

In `@packages/bot/src/spotify/spotifyApi.ts`:
- Around line 136-149: The current logic stores null permanently on any non-OK
response or exception, causing transient Spotify errors to be treated as
permanent misses; update the code around artistPopularityCache, artistName and
the try/catch in the function that fetches popularity so that only genuine "not
found" results (e.g., a successful fetch with no artist items) are cached as
null, while transport failures and 429/5xx responses are either not cached or
cached with a short TTL; specifically, remove or change the
artistPopularityCache.set(artistName, null) in the catch and the !res.ok branch
to distinguish permanent misses from transient failures and use a short
expiration or no-cache for transient errors.

In `@packages/bot/src/utils/music/autoplay/lastFmSeeds.ts`:
- Around line 146-161: The slice allocation uses totalWeight from the entire
weights Map and forces a minimum of 1 per user which causes
under/over-allocation when userIds is a filtered subset or when count <
userIds.length; fix by computing totalWeight only over the participating userIds
(sum weights.get(id) for each id in userIds), stop forcing Math.max(1, …) so
initial fractional allocations can be zero, then convert fractions to integers
using the largest-remainder method (floor all fractional allocations, compute
remainder slots = count - sum(floors), and distribute +1 to the users with
largest fractional remainders) so sliceSizes (the array computed from
userIds/weights/count) always sums to count and never arbitrarily advances
offsets for discarded tracks.

In `@packages/bot/src/utils/music/queueManipulation.spec.ts`:
- Around line 97-98: The spec file declares the same mock twice
(dislikedTrackWeightsMock) causing a parse error; remove the duplicate
declaration so there's only a single const dislikedTrackWeightsMock = jest.fn()
in the scope (locate both occurrences of dislikedTrackWeightsMock in the test
file and delete the redundant one), then run tests to confirm parsing succeeds.

In `@packages/bot/src/utils/music/queueManipulation.ts`:
- Around line 1511-1527: The same-artist scoring block is broken: there are
stray unconditional "session novelty" increments and a mismatched brace causing
the TS1128 error, and the album-cohesion check uses sharedTitleTokenScore() >
0.4 which can never be true (that function is capped at 0.2). Fix by removing
the two stray score/reasons lines and restoring the correct else-if/else brace
structure so the session-novelty bonus is only applied in the branch where
candidateArtist !== currentArtist and not in the same-artist path; then change
the title similarity threshold in the sharedTitleTokenScore() check (used inside
the candidateArtist === currentArtist branch) to a reachable value (e.g., > 0.15
or >= 0.2) so the album match bonus can actually trigger. Ensure you touch the
block referencing candidateArtist, currentArtist, sharedTitleTokenScore,
recentArtists, score, and reasons.
- Around line 506-514: The code redundantly calls
getTrackAudioFeatures(currentTrack, requestedBy?.id ?? '') instead of reusing
the previously computed currentFeatures (fetched at lines 367-371), which can
cache a global null and block later retries; fix by passing the existing
currentFeatures into enrichWithAudioFeatures rather than calling
getTrackAudioFeatures again—locate the call that assigns currentAudioFeatures
and replace it so enrichWithAudioFeatures(selected, requestedBy?.id ?? '',
currentFeatures) is used (ensure variable name currentFeatures is in scope where
enrichWithAudioFeatures is invoked).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 687960dd-ab69-42cc-af76-fe71bf264a01

📥 Commits

Reviewing files that changed from the base of the PR and between c6e9727 and f3ac5e4.

📒 Files selected for processing (6)
  • packages/bot/src/spotify/spotifyApi.spec.ts
  • packages/bot/src/spotify/spotifyApi.ts
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.spec.ts
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.ts
  • packages/bot/src/utils/music/queueManipulation.spec.ts
  • packages/bot/src/utils/music/queueManipulation.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: SonarCloud Scan
🧰 Additional context used
🪛 Biome (2.4.10)
packages/bot/src/spotify/spotifyApi.spec.ts

[error] 1-1: Expected a statement but instead found '======='.

(parse)


[error] 260-260: Expected an expression but instead found '>>>'.

(parse)


[error] 260-260: Expected an expression but instead found '>'.

(parse)


[error] 260-260: expected , but instead found :

(parse)

🪛 GitHub Actions: CI/CD Pipeline
packages/bot/src/utils/music/queueManipulation.ts

[error] 1616-1616: TypeScript compilation failed with TS1128: Declaration or statement expected.

Comment thread packages/bot/src/spotify/spotifyApi.spec.ts Outdated
Comment thread packages/bot/src/spotify/spotifyApi.spec.ts Outdated
Comment thread packages/bot/src/spotify/spotifyApi.ts
Comment thread packages/bot/src/utils/music/autoplay/lastFmSeeds.ts
Comment thread packages/bot/src/utils/music/queueManipulation.spec.ts Outdated
Comment thread packages/bot/src/utils/music/queueManipulation.ts
Comment thread packages/bot/src/utils/music/queueManipulation.ts
resetMocks:true resets mock implementations to bare jest.fn() returning
undefined. Calling .catch() on undefined throws TypeError. Wrapping in
Promise.resolve() makes the chain safe regardless of return type.
The file had an orphaned '=======' merge marker at the start and was
truncated at 200 lines. Replaced with a complete spec that preserves
all original getAudioFeatures/searchSpotifyTrack tests and adds the
new getBatchAudioFeatures/getArtistPopularity tests (24 total).
…boost

- enrichWithAudioFeatures: test with Spotify candidate URLs so batch
  feature scoring path (lines 1153-1188) is exercised
- popular mode artist popularity: covers discover/popular token fetch
  and getArtistPopularity call (lines 522-544)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/bot/src/utils/music/queueManipulation.ts (1)

506-514: ⚠️ Potential issue | 🟡 Minor

Redundant fetch of audio features for current track.

currentFeatures is already computed at lines 367-371. Calling getTrackAudioFeatures again here is wasteful and can cache a global null when requestedBy?.id is empty (since '' is passed instead of being guarded).

Proposed fix
-        const currentAudioFeatures = await getTrackAudioFeatures(
-            currentTrack,
-            requestedBy?.id ?? '',
-        )
         const enriched = await enrichWithAudioFeatures(
             selected,
             requestedBy?.id ?? '',
-            currentAudioFeatures,
+            currentFeatures,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/music/queueManipulation.ts` around lines 506 - 514,
The code redundantly calls getTrackAudioFeatures for currentTrack even though
currentFeatures was already computed earlier (see currentFeatures variable);
remove this duplicate call and pass the already-computed currentFeatures into
enrichWithAudioFeatures instead of re-fetching, and avoid passing an empty
string for requester id—use requestedBy?.id or undefined (not '') when calling
enrichWithAudioFeatures so a global null/empty cache isn't stored; update the
call site referencing currentTrack, selected, enrichWithAudioFeatures, and
getTrackAudioFeatures accordingly.
🧹 Nitpick comments (1)
packages/bot/src/utils/music/queueManipulation.ts (1)

520-522: Unnecessary Promise.resolve() wrapper.

spotifyLinkService.getValidAccessToken already returns a Promise, so Promise.resolve() adds no value.

Simplify
-            const token = await Promise.resolve(spotifyLinkService
-                .getValidAccessToken(requestedBy.id))
-                .catch(() => null)
+            const token = await spotifyLinkService
+                .getValidAccessToken(requestedBy.id)
+                .catch(() => null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bot/src/utils/music/queueManipulation.ts` around lines 520 - 522,
Remove the unnecessary Promise.resolve wrapper around
spotifyLinkService.getValidAccessToken in the token assignment: directly await
spotifyLinkService.getValidAccessToken(requestedBy.id) and keep the existing
error handling (e.g., .catch(() => null) or a try/catch) so the behavior remains
the same; update the const token assignment in queueManipulation.ts where
spotifyLinkService.getValidAccessToken(requestedBy.id) is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/bot/src/utils/music/queueManipulation.ts`:
- Around line 506-514: The code redundantly calls getTrackAudioFeatures for
currentTrack even though currentFeatures was already computed earlier (see
currentFeatures variable); remove this duplicate call and pass the
already-computed currentFeatures into enrichWithAudioFeatures instead of
re-fetching, and avoid passing an empty string for requester id—use
requestedBy?.id or undefined (not '') when calling enrichWithAudioFeatures so a
global null/empty cache isn't stored; update the call site referencing
currentTrack, selected, enrichWithAudioFeatures, and getTrackAudioFeatures
accordingly.

---

Nitpick comments:
In `@packages/bot/src/utils/music/queueManipulation.ts`:
- Around line 520-522: Remove the unnecessary Promise.resolve wrapper around
spotifyLinkService.getValidAccessToken in the token assignment: directly await
spotifyLinkService.getValidAccessToken(requestedBy.id) and keep the existing
error handling (e.g., .catch(() => null) or a try/catch) so the behavior remains
the same; update the const token assignment in queueManipulation.ts where
spotifyLinkService.getValidAccessToken(requestedBy.id) is used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 466e6fe0-72d7-488a-9850-1866c6d94d88

📥 Commits

Reviewing files that changed from the base of the PR and between f3ac5e4 and 4cfe14b.

📒 Files selected for processing (6)
  • packages/bot/src/spotify/spotifyApi.spec.ts
  • packages/bot/src/spotify/spotifyApi.ts
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.spec.ts
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.ts
  • packages/bot/src/utils/music/queueManipulation.spec.ts
  • packages/bot/src/utils/music/queueManipulation.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.spec.ts
  • packages/bot/src/utils/music/autoplay/lastFmSeeds.ts
  • packages/bot/src/spotify/spotifyApi.spec.ts
  • packages/bot/src/utils/music/queueManipulation.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Quality Gates
  • GitHub Check: SonarCloud Scan
🔇 Additional comments (8)
packages/bot/src/spotify/spotifyApi.ts (2)

136-150: Caching null on transient errors persists failures indefinitely.

A 429/5xx or network error caches null forever for that artist, preventing popularity boosts until restart. Only cache null for genuine "not found" (200 with empty items), not transport failures.


54-110: LGTM – batch fetch handles edge cases well.

The function correctly:

  • Returns early for empty input
  • Respects Spotify's 100-track limit via slice(0, 100)
  • Validates required id, energy, and valence before mapping
  • Defaults optional fields appropriately
packages/bot/src/utils/music/queueManipulation.ts (6)

1512-1525: Album cohesion bonus can never trigger – threshold exceeds function cap.

sharedTitleTokenScore() returns Math.min(0.2, matches * 0.05) (line 1626), capping at 0.2. The album match condition at line 1518 checks titleSim > 0.4, which is unreachable.

Either lower the threshold to a reachable value (e.g., > 0.15) or increase the cap in sharedTitleTokenScore.

Option 1: Lower threshold
-        if (titleSim > 0.4) {
+        if (titleSim > 0.15) {
             score += 0.12
             reasons.push('album match')
         }
Option 2: Increase function cap
-    return Math.min(0.2, matches * 0.05)
+    return Math.min(0.5, matches * 0.1)

Also applies to: 1616-1627


17-23: LGTM – imports align with new functionality.

The additions of getBatchAudioFeatures, getArtistPopularity, and SpotifyAudioFeatures type are necessary for the batch enrichment and artist popularity features.


400-404: LGTM – contribution weights correctly scoped to multi-user sessions.

The weights are only built when vcMemberIds.length > 1, avoiding unnecessary computation for single-user sessions, and properly passed through to consumeBlendedSeedSlice.

Also applies to: 422-422


516-546: LGTM – artist popularity boost logic is correct.

The boost is correctly applied only for discover/popular modes, limited to top 3 candidates, and the thresholds (≥70 for popular, ≤40 for discover) align with PR objectives. Re-sorting after adjustment ensures correct ordering.


1143-1189: LGTM – batch audio feature enrichment is well-structured.

The function correctly:

  • Early-returns when preconditions aren't met
  • Extracts Spotify IDs only from valid Spotify URLs
  • Applies tiered scoring based on energy/valence deltas
  • Returns sorted results

1382-1407: LGTM – contribution weight calculation is sound.

The algorithm ensures:

  • Minimum weight of 1 for members with no contributions
  • Proper normalization via scale factor
  • Equal weights when all members have equal contributions

@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev dismissed coderabbitai[bot]’s stale review April 13, 2026 03:57

Addressed — all tests passing, coverage fixed

@LucasSantana-Dev
LucasSantana-Dev merged commit 8236e0f into main Apr 13, 2026
12 checks passed
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
…esion (phases B/D/E) (#579)

* feat(bot): spotify audio batch features, artist popularity, album cohesion scoring

phase b — audio feature batch matching:
- getBatchAudioFeatures(): fetches up to 100 track features in one Spotify API call
- enrichWithAudioFeatures(): runs after selectDiverseCandidates; extracts Spotify IDs from
  track URLs, batch-fetches features, applies energy+valence delta scoring:
  energyDelta<0.15 && valenceDelta<0.20 → +0.15, partial match → +0.07, mismatch >0.6 → -0.10

phase d — artist popularity weighting:
- getArtistPopularity(): searches Spotify, returns 0-100 popularity with module-level cache
- top 3 candidates enriched post-selection when mode=popular or discover:
  popular mode + popularity≥70 → +0.12; discover mode + popularity≤40 → +0.12

phase e — album cohesion:
- in calculateRecommendationScore: same-artist candidates with title token similarity >0.4
  get +0.12 album match boost (same album/era without needing album metadata field)

18 new tests in spotifyApi.spec.ts covering all new functions and error paths

* feat(bot): vc contribution weighting for multi-user taste blend

- buildVcContributionWeights(): counts manual tracks requested per vc member,
  normalises so total weight = member count, baseline 1 for zero-contribution members
- consumeBlendedSeedSlice(): accepts optional weights map and allocates proportional
  seed slices (min 1 per user), falls back to equal distribution when no weights
- heavy listener in a 3-person vc now gets proportionally more seed slots than
  someone who joined but hasn't requested anything yet
- 4 new tests: equal weights, heavy listener, zero-contrib baseline, weighted blend

* fix: restore Spotify batch audio features lost during rebase

* fix: wrap getValidAccessToken in Promise.resolve before .catch()

resetMocks:true resets mock implementations to bare jest.fn() returning
undefined. Calling .catch() on undefined throws TypeError. Wrapping in
Promise.resolve() makes the chain safe regardless of return type.

* fix: restore spotifyApi.spec.ts corrupted by rebase conflict marker

The file had an orphaned '=======' merge marker at the start and was
truncated at 200 lines. Replaced with a complete spec that preserves
all original getAudioFeatures/searchSpotifyTrack tests and adds the
new getBatchAudioFeatures/getArtistPopularity tests (24 total).

* test: add coverage for enrichWithAudioFeatures and artist popularity boost

- enrichWithAudioFeatures: test with Spotify candidate URLs so batch
  feature scoring path (lines 1153-1188) is exercised
- popular mode artist popularity: covers discover/popular token fetch
  and getArtistPopularity call (lines 522-544)
LucasSantana-Dev added a commit that referenced this pull request Jun 13, 2026
## Summary

Add test coverage for existing batch audio-features and artist
popularity cache optimizations:
- Verify getBatchAudioFeatures() batches multiple track IDs into one API
call (not N separate calls)
- Verify getArtistPopularity() cache hit/miss behavior avoids duplicate
Spotify API fetches

Both features were implemented in commit #579 but lacked explicit test
verification.

## Test results
- spotifyApi.spec.ts: all 26 tests pass (including 2 new)
- New test: "batches multiple track IDs into one fetch call" ✓
- New test: "cache hit/miss behavior avoids duplicate fetches" ✓
- No regressions in existing test suites

@cubic-dev-ai

Closes #1091

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds tests to lock in batching and caching optimizations:
`getBatchAudioFeatures` makes one Spotify request for multiple track
IDs, and `getArtistPopularity` returns from cache on repeat calls. Adds
test-only `_resetPopularityCache` for isolation and removes unused
`_resetGenresCache`.

Closes #1091.

<sup>Written for commit 9cc11b2.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/LucasSantana-Dev/Lucky/pull/1375?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

This branch was successfully deployed

1 active deployment
Preview — 32c45199 Deployed Apr 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot enhancement New feature or request size/xl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant