feat(catalogs): artist_account_id filter + no-cap pagination on GET /api/catalogs/measurements
Implements the data plumbing v2: artist-scoped valuation api item of recoupable/chat#1850 — the contract is recoupable/docs#267 (merge that first).
What changed (same read path as api#757, two behaviors)
1. Fix: the silent 1,000-row cap (P1)
The endpoint computed total_streams + the valuation band over at most 1,000 rows — the Supabase default limit — with no error. Live repro on a 2,679-song catalog: exactly 1,000 measurements / 4.83B streams / $39.3M mid vs SQL ground truth 2,679 / 16.31B / ≈$132.5M (3.4× undervalued): https://github.com/recoupable/api/pull/757#issuecomment-4898568413
Now every read on the path paginates to exhaustion before dedupe/derivation:
lib/supabase/catalog_songs/selectCatalogSongTitles.ts—.range()loop (the tracklist itself capped at 1,000)lib/supabase/song_measurements/selectAllSongMeasurements.ts(new) — ISRC list chunked at 500 per query (boundedINclause / URL length) and each chunk.range()-paginated to exhaustion, with a stable secondary order for deterministic pageslib/supabase/song_artists/selectSongArtistIsrcs.ts(new) —.range()loop
2. Feature: optional artist_account_id query param
- Validated as uuid (
400on malformed) invalidateGetCatalogMeasurementsQuery - When present, the read covers only the catalog's songs linked to that artist account via
song_artists(catalog_songs ∩ song_artists, computed in the newlib/catalog/resolveCatalogSongsInScope.ts); absent → whole catalog (unchanged behavior) - Response echoes the applied filter: new
artist_account_idfield (uuid when scoped,nullwhen whole-catalog) so clients can verify the response scope before rendering artist-labeled numbers — pre-v2 deployments ignore the unknown param, and without the echo a client would show whole-catalog money under an artist label (the trust bug chat#1850 exists to kill)
Verification
- TDD throughout — RED confirmed before GREEN for every unit (pagination via >1,000-row fixtures, chunking, intersection, validator, handler echo)
- Full suite: 716 files / 3,945 tests passing
pnpm exec tsc --noEmit: 200 errors, all pre-existing in test files (0 new; 0 in touched files)pnpm lintclean- Preview verification against the live
[TEST] Full Roster Catalog (aggregate)fixture posted as a PR comment once the preview deploy is Ready
Merge order
recoupable/docs#267 (contract) → this PR → chat#1852 rework (hero consumes the filter + echo). Refs recoupable/chat#1850.
🤖 Generated with Claude Code
Summary by cubic
Reworks GET /api/catalogs/{catalogId}/measurements to return a paginated page of latest-per-ISRC Spotify playcounts with whole-scope totals and a SQL-derived valuation (no row cap). Adds optional artist scoping and moves catalogId to the path.
-
New Features
- Add
artist_account_id(UUID; 400 on malformed) to scope results; echoed as UUID ornull. - Add
pageandlimit(default 1/50, max 100; 400 on invalid). Response includesmeasurements,pagination,measured_song_count,total_streams,valuation,artist_account_id, andcatalog_age_years.
- Add
-
Refactors
- Endpoint now uses a path param: GET /api/catalogs/{catalogId}/measurements. Validator takes the path id (UUID) and ignores any
catalogIdin the query. - Auth moved into the validator; unauthenticated requests now 401 before param validation errors.
- Replace looped reads with SQL RPCs:
get_catalog_measurements_aggregatefor whole-scope totals andget_catalog_measurements_pagefor the page (sorted by playcount desc). Aggregates cover the entire scope regardless of page size.
- Endpoint now uses a path param: GET /api/catalogs/{catalogId}/measurements. Validator takes the path id (UUID) and ignores any
Summary by CodeRabbit
-
New Features
- Added a catalog measurements endpoint that supports CORS preflight and returns measurement data by catalog.
- Added pagination and optional artist filtering for measurements results.
- Included summary metrics such as total streams, measured song count, valuation, and artist account details in the response.
-
Bug Fixes
- Improved validation and error handling for invalid requests.
- Added safer handling when measurement data is unavailable.