feat(migrations): catalog measurements read functions (aggregate + paginated page)#42
Conversation
…ggregate + paginated latest-per-ISRC page (chat#1850) Two STABLE sql functions backing GET /api/catalogs/measurements v2: - get_catalog_measurements_aggregate(p_catalog, p_artist): single SQL aggregate (count + sum) over the latest-per-ISRC subquery, whole scope, optional song_artists filter - get_catalog_measurements_page(p_catalog, p_artist, p_limit, p_offset): one page of latest-per-ISRC rows sorted by playcount desc DISTINCT ON is not expressible through supabase-js and the app-code loop-to-exhaustion read was rejected (chat#1850 decision 2026-07-07). Applied to the shared project 2026-07-07 via MCP (schema_migrations version aligned to 20260707120000); verified against the [TEST] Full Roster Catalog: whole 2,679/16,308,837,441; Elvis Crespo 322/1,799,402,184. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Updates to Preview Branch (feat/catalog-measurements-rpc) ↗︎
Tasks are run on every commit but only new migration files are pushed.
View logs for this Workflow Run ↗︎. |
…aller-paginated page (chat#1850) Amendment per the 2026-07-07 decision on chat#1850: the app-code .range() loop-to-exhaustion is rejected. The read path becomes: - aggregates (measured_song_count, total_streams -> valuation band) from the get_catalog_measurements_aggregate RPC: one SQL aggregate over the latest-per-ISRC subquery, whole scope, no row cap - the measurements array from the get_catalog_measurements_page RPC: page/limit params (default 1/50, limit max 100, 400 on invalid), rows sorted by playcount desc, same pagination envelope as GET /api/catalogs/songs - artist_account_id filter + response echo unchanged Both RPCs ship in recoupable/database#42 (applied to the shared project). Deletes the loop-based helpers (selectAllSongMeasurements, selectSongArtistIsrcs, selectCatalogSongTitles, resolveCatalogSongsInScope, latestMeasurementsPerIsrc) whose only consumer was this handler. TDD: RED confirmed before GREEN per unit; full suite 3,940 tests, tsc 200 pre-existing errors (0 new), lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…api/catalogs/measurements (#763) * feat(catalogs): artist_account_id filter + no-cap pagination on GET /api/catalogs/measurements (chat#1850) Two fixes on the same read path (v2 of api#757): - FIX the silent 1,000-row cap: the catalog tracklist, the measurement series, and the artist-link read all paginate to exhaustion with .range() loops (and the measurements IN clause is chunked at 500 ISRCs to stay under URL limits) BEFORE dedupe/derivation, so total_streams and the valuation band cover every measured song. Live repro was a 2,679-song catalog valued 3.4x low off exactly 1,000 rows. - ADD optional artist_account_id (uuid, 400 on malformed): scopes measurements + valuation to the catalog's songs linked to that artist account via song_artists (catalog_songs ∩ song_artists). The response echoes the applied filter as artist_account_id (null when unfiltered) so clients can verify the scope before rendering artist-labeled money. Per the docs contract in recoupable/docs#267. TDD: RED confirmed before GREEN for every unit; full suite 3,945 tests, tsc 0 new errors, lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(catalogs): measurements read = whole-scope SQL aggregate + caller-paginated page (chat#1850) Amendment per the 2026-07-07 decision on chat#1850: the app-code .range() loop-to-exhaustion is rejected. The read path becomes: - aggregates (measured_song_count, total_streams -> valuation band) from the get_catalog_measurements_aggregate RPC: one SQL aggregate over the latest-per-ISRC subquery, whole scope, no row cap - the measurements array from the get_catalog_measurements_page RPC: page/limit params (default 1/50, limit max 100, 400 on invalid), rows sorted by playcount desc, same pagination envelope as GET /api/catalogs/songs - artist_account_id filter + response echo unchanged Both RPCs ship in recoupable/database#42 (applied to the shared project). Deletes the loop-based helpers (selectAllSongMeasurements, selectSongArtistIsrcs, selectCatalogSongTitles, resolveCatalogSongsInScope, latestMeasurementsPerIsrc) whose only consumer was this handler. TDD: RED confirmed before GREEN per unit; full suite 3,940 tests, tsc 200 pre-existing errors (0 new), lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(catalogs): catalogId moves to the path — GET /api/catalogs/[catalogId]/measurements (chat#1850) REST amendment on chat#1850: path for identity, query for modifiers — mirrors the research measurements family (app/api/research/albums/[id]/measurements). The route becomes a dynamic segment; the validator takes the path id (uuid, 400 on malformed; a catalogId smuggled into the query string is ignored) and the query carries only artist_account_id/page/limit. A missing id no longer routes (404 from Next), so the 'catalogId is required' 400 is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(catalogs): move auth into validateGetCatalogMeasurementsQuery (SRP) Review feedback on #763: the handler was doing auth validation itself. The validator now owns both auth and input validation (auth first), matching the measurements-family validator convention (validateGetResearchTrackHistoricStatsRequest et al) and returns { accountId, ...params }. Behavior note: an unauthenticated request with malformed params now 401s before the 400 param check, same as the research siblings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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
STABLEsql 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 ONis not expressible through supabase-js, so both reads live here.Status
Already applied to the shared project (via MCP,
schema_migrations.versionaligned to20260707120000) so the api PR preview can verify. Verified against the[TEST] Full Roster Catalog (aggregate)7d3e5c35…:b1814076…d85f9606…ebae4bb9…6f1d797f…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.
Written for commit 6e6011b. Summary will update on new commits.