feat(migrations): catalog measurements read functions (aggregate + paginated page)
Backs the data plumbing v2 amendment of recoupable/chat#1850 (decision 2026-07-07: whole-scope aggregates via a single SQL aggregate; app-code loop-to-exhaustion rejected).
What
One migration adding two STABLE sql functions (RPC precedent: claim_songstats_backfill_rows, deduct_credits_with_audit):
get_catalog_measurements_aggregate(p_catalog uuid, p_artist uuid default null)→(measured_song_count bigint, total_streams numeric): count + sum over the latest-per-ISRC (DISTINCT ON) subquery for the catalog's songs, optionally restricted to those linked to the artist account viasong_artists.get_catalog_measurements_page(p_catalog uuid, p_artist uuid default null, p_limit int default 50, p_offset int default 0)→ latest-per-ISRC rows(isrc, title, playcount, measured_at)sorted by playcount desc, for the caller-paginatedmeasurementsarray.
DISTINCT ON is not expressible through supabase-js, so both reads live here.
Status
Already applied to the shared project (via MCP, schema_migrations.version aligned to 20260707120000) so the api PR preview can verify. Verified against the [TEST] Full Roster Catalog (aggregate) 7d3e5c35…:
| Scope | measured_song_count | total_streams |
|---|---|---|
| whole catalog | 2,679 | 16,308,837,441 |
Elvis Crespo b1814076… | 322 | 1,799,402,184 |
September Mourning d85f9606… | 40 | 21,325,663 |
Apache ebae4bb9… | 208 | 742,453,110 |
J Alvarez 6f1d797f… | 608 | 6,717,937,120 |
All match the independent read-only SQL ground truth exactly.
Merge order
recoupable/docs#267 (contract) → this migration → recoupable/api#763 (implementation) → chat#1852. Refs recoupable/chat#1850.
🤖 Generated with Claude Code
Summary by cubic
Adds two STABLE SQL RPCs to power GET /api/catalogs/measurements v2: a whole-scope aggregate and a paginated latest-per-ISRC page. This delivers accurate totals and ranked rows in one query, avoiding app-side loops and supabase-js limits.
- New Features
- get_catalog_measurements_aggregate(p_catalog, p_artist?): returns measured_song_count and total_streams across the latest-per-ISRC Spotify playcount; optional artist filter.
- get_catalog_measurements_page(p_catalog, p_artist?, p_limit=50, p_offset=0): returns latest-per-ISRC rows (isrc, title, playcount, measured_at) sorted by playcount desc; paginated; optional artist filter.