docs: add BPM enrichment design spec

This commit is contained in:
2026-05-25 18:33:10 +02:00
parent 6ee9cde391
commit e69e56e10a

View File

@@ -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).