From e69e56e10a638a56f421bfff84546faa0037dd97 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Mon, 25 May 2026 18:33:10 +0200 Subject: [PATCH] docs: add BPM enrichment design spec --- .../specs/2026-05-25-bpm-enrichment-design.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-25-bpm-enrichment-design.md diff --git a/docs/superpowers/specs/2026-05-25-bpm-enrichment-design.md b/docs/superpowers/specs/2026-05-25-bpm-enrichment-design.md new file mode 100644 index 0000000..2f1fdd2 --- /dev/null +++ b/docs/superpowers/specs/2026-05-25-bpm-enrichment-design.md @@ -0,0 +1,76 @@ +# BPM Enrichment Design + +**Date:** 2026-05-25 +**Status:** Approved + +## Goal + +Add a `bpm` field to every track object returned by the MCP server, sourced from Spotify's `/audio-features` endpoint. + +## Scope + +Three tools return track objects and must be updated: +- `get_playlist_tracks` +- `list_saved_tracks` +- `search_tracks` + +No new MCP tools. No changes to `server.py`. + +## Architecture + +### New helper: `_enrich_with_bpm(sp, tracks)` + +Location: `spotify_client.py` + +- Takes a `spotipy.Spotify` client and a list of already-parsed track dicts. +- Extracts track IDs from each dict's `"id"` field. +- Slices into chunks of 100 (API limit) and calls `sp.audio_features(ids)` once per chunk. +- Matches results back by position and sets `track["bpm"]` to the rounded tempo float. +- If the API returns `None` for a track (features unavailable), sets `bpm` to `null`. +- Mutates the list in place; returns nothing. + +### Changes to track parsers + +Each of the three functions adds `"id": track["id"]` to the dict it builds (currently absent from the output). After the full track list is assembled, each function calls `_enrich_with_bpm(sp, results)` before returning. + +## Data Shape + +Before: +```json +{ + "name": "oh baby", + "artists": ["LCD Soundsystem"], + "album": "american dream", + "duration_ms": 349452, + "duration": "5:49", + "popularity": null, + "uri": "spotify:track:53PkA8aXiwH4ppa0V0iO7o", + "added_at": "2026-05-22T17:41:18Z" +} +``` + +After: +```json +{ + "name": "oh baby", + "artists": ["LCD Soundsystem"], + "album": "american dream", + "duration_ms": 349452, + "duration": "5:49", + "popularity": null, + "uri": "spotify:track:53PkA8aXiwH4ppa0V0iO7o", + "id": "53PkA8aXiwH4ppa0V0iO7o", + "added_at": "2026-05-22T17:41:18Z", + "bpm": 128.0 +} +``` + +## Error Handling + +- `sp.audio_features()` may return `None` entries for tracks with no features (rare, e.g. local files, some podcasts). These get `bpm: null`. +- Any exception from `sp.audio_features()` propagates naturally — same behaviour as the rest of the client. + +## API Cost + +- `get_playlist_tracks` / `list_saved_tracks`: 1 extra API call per 100 tracks (batched). +- `search_tracks`: at most 1 extra call (max 50 results per search).