Skip to content

feat(migrations): catalog measurements read functions (aggregate + paginated page)#42

Merged
sweetmantech merged 1 commit into
mainfrom
feat/catalog-measurements-rpc
Jul 7, 2026
Merged

feat(migrations): catalog measurements read functions (aggregate + paginated page)#42
sweetmantech merged 1 commit into
mainfrom
feat/catalog-measurements-rpc

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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 via song_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-paginated measurements array.

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 migrationrecoupable/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.

Written for commit 6e6011b. Summary will update on new commits.

Review in cubic

…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>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b187c24-9d56-4b95-9180-b2f64736cade

📥 Commits

Reviewing files that changed from the base of the PR and between dcd0695 and 6e6011b.

📒 Files selected for processing (1)
  • supabase/migrations/20260707120000_catalog_measurements_read_functions.sql
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/catalog-measurements-rpc

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Jul 7, 2026

Copy link
Copy Markdown

Updates to Preview Branch (feat/catalog-measurements-rpc) ↗︎

Deployments Status Updated
Database Tue, 07 Jul 2026 12:28:55 UTC
Services Tue, 07 Jul 2026 12:28:55 UTC
APIs Tue, 07 Jul 2026 12:28:55 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 07 Jul 2026 12:29:05 UTC
Migrations Tue, 07 Jul 2026 12:29:10 UTC
Seeding Tue, 07 Jul 2026 12:29:10 UTC
Edge Functions Tue, 07 Jul 2026 12:29:10 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

sweetmantech added a commit to recoupable/api that referenced this pull request Jul 7, 2026
…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>
@sweetmantech sweetmantech merged commit d080fe9 into main Jul 7, 2026
3 checks passed
sweetmantech added a commit to recoupable/api that referenced this pull request Jul 7, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant