feat(catalogs): artist_account_id filter + no-cap pagination on GET /api/catalogs/measurements

Merged
SW
4mo ago
+588-318

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 (bounded IN clause / URL length) and each chunk .range()-paginated to exhaustion, with a stable secondary order for deterministic pages
  • lib/supabase/song_artists/selectSongArtistIsrcs.ts (new) — .range() loop

2. Feature: optional artist_account_id query param

  • Validated as uuid (400 on malformed) in validateGetCatalogMeasurementsQuery
  • 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 new lib/catalog/resolveCatalogSongsInScope.ts); absent → whole catalog (unchanged behavior)
  • Response echoes the applied filter: new artist_account_id field (uuid when scoped, null when 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 lint clean
  • 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 or null.
    • Add page and limit (default 1/50, max 100; 400 on invalid). Response includes measurements, pagination, measured_song_count, total_streams, valuation, artist_account_id, and catalog_age_years.
  • Refactors

    • Endpoint now uses a path param: GET /api/catalogs/{catalogId}/measurements. Validator takes the path id (UUID) and ignores any catalogId in the query.
    • Auth moved into the validator; unauthenticated requests now 401 before param validation errors.
    • Replace looped reads with SQL RPCs: get_catalog_measurements_aggregate for whole-scope totals and get_catalog_measurements_page for the page (sorted by playcount desc). Aggregates cover the entire scope regardless of page size.

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.