Skip to content

docs(catalogs): v2 measurements contract — artist_account_id filter + echo + no-cap semantics#267

Merged
sweetmantech merged 4 commits into
mainfrom
docs/catalog-measurements-artist-filter
Jul 7, 2026
Merged

docs(catalogs): v2 measurements contract — artist_account_id filter + echo + no-cap semantics#267
sweetmantech merged 4 commits into
mainfrom
docs/catalog-measurements-artist-filter

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Amends the GET /api/catalogs/measurements contract (docs#265, merged) for the data plumbing v2: artist-scoped valuation bucket of recoupable/chat#1850.

What changed (all in api-reference/openapi/releases.json; the reference page api-reference/songs/catalog-measurements.mdx is frontmatter-only and unchanged)

  • New optional query param artist_account_id (uuid): when present, measurements + valuation cover only the catalog's songs linked to that artist account via song_artists; when absent, the whole catalog. A song linked to multiple artists counts for each. Malformed values → 400 (400 description updated).
  • New response field artist_account_id (uuid, nullable): echoes the applied filter — the uuid when the response was artist-scoped, null when whole-catalog. Lets clients (the chat homepage hero) verify the response scope before rendering artist-labeled numbers, instead of showing wrong-scoped money against a pre-v2 deployment that ignores the unknown param.
  • Explicit no-cap semantics: measurements and total_streams now state they cover ALL matching songs with no row cap. The current implementation silently truncates at 1,000 rows (live repro: feat(catalogs): GET /api/catalogs/measurements — per-ISRC latest playcounts + derived valuation band (chat#1850) api#757 (comment)) — being fixed in the api v2 PR.

The OpenAPI JSON diff is purely additive/amending on the measurements path + response schema; every other path/schema is byte-identical (verified by structural diff after a parse round-trip).

Amended 2026-07-07

Per the dated decision on chat#1850: whole-scope SQL aggregates + a caller-paginated measurements array replace the loop-to-exhaustion read. The contract now documents page/limit params (defaults 1/50, max 100), the pagination envelope (same shape as catalog songs), and a top-level measured_song_count; the aggregates (measured_song_count, total_streams, valuation) explicitly cover the ENTIRE scope regardless of pagination. Filter + echo semantics unchanged. Review feedback applied: the endpoint description stays the short docs#265 original; all details live on params/schemas.

Renamed 2026-07-07

The endpoint is now GET /api/catalogs/{catalogId}/measurements — path for identity, query for the optional modifiers (artist_account_id, page, limit), matching the research measurements family (REST decision on recoupable/chat#1850). Review feedback also applied: short endpoint description, details on params/schemas.

Merge order

This docs PR merges first, then recoupable/database#42 (the RPCs), then the api v2 implementation PR (recoupable/api#763), then the chat#1852 hero rework. Contract-first per documentation-driven development.

Refs recoupable/chat#1850.

🤖 Generated with Claude Code


Summary by cubic

Respecs the v2 catalog measurements API: moves catalogId into the path and adds artist-scoped reads with caller pagination while keeping aggregates full-scope with no row cap. Supports chat#1850 with artist-scoped valuation and safer client rendering via scope echoes.

  • New Features

    • Optional artist_account_id query param; response echoes artist_account_id; malformed values return 400.
    • Caller-paginated measurements with page/limit (default 1/50, max 100) and a pagination envelope; rows sorted by play count; invalid values return 400.
    • New measured_song_count; aggregates (measured_song_count, total_streams, valuation) cover the entire scope regardless of pagination — no row cap.
  • Refactors

    • Endpoint is now GET /api/catalogs/{catalogId}/measurements; catalogId is a required path param (uuid).
    • 400 now covers malformed catalogId and invalid page/limit; endpoint description stays short with details in params/schemas.

Written for commit acb9068. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Documentation
    • Clarified the catalog measurements API to support optional artist-level filtering.
    • Documented that results are exhaustive with no row cap and that only catalog songs linked to the selected artist are included.
    • Added a response field that echoes the applied artist filter, or shows no filter when unscoped.
    • Expanded error details for invalid artist identifier input.

… filter, echo field, explicit no-cap semantics (chat#1850)

Amends the GET /api/catalogs/measurements contract from docs#265:
- optional query param artist_account_id (uuid): scope measurements +
  valuation to the catalog's songs linked to that artist via song_artists;
  absent = whole catalog. Malformed values are a 400.
- response field artist_account_id (uuid, nullable): echoes the applied
  filter so clients can verify the response scope before trusting it.
- measurements/total_streams explicitly cover ALL matching songs — no
  row cap (the current impl silently truncates at 1,000; fixed api-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 6, 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 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updated the OpenAPI specification for GET /api/catalogs/measurements to document optional artist_account_id scoping, clarify exhaustive/no-row-cap read behavior, expand 400 error documentation, and add a nullable artist_account_id field to the CatalogMeasurementsResponse schema.

Changes

Artist scoping documentation

Layer / File(s) Summary
Endpoint parameter and error documentation
api-reference/openapi/releases.json
Adds optional artist_account_id UUID query parameter, expands endpoint description for scoping and no-row-cap behavior, and updates the 400 response description to cover malformed artist_account_id.
Response schema updates
api-reference/openapi/releases.json
Adds nullable artist_account_id field to CatalogMeasurementsResponse and clarifies measurements/total_streams field descriptions.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the OpenAPI docs change for catalog measurements, including the artist filter, echoed scope, and no-cap semantics.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/catalog-measurements-artist-filter

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.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
api-reference/openapi/releases.json (1)

2469-2482: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider marking artist_account_id (and other core fields) as required in the response schema.

The description explicitly tells clients to verify this echo before treating numbers as artist-scoped, but the schema has no required list, so the field is technically optional to omit. Adding artist_account_id to a required array (with nullable: true already in place) would make the contract clients are told to depend on enforceable in the spec itself.

♻️ Proposed schema tweak
       "CatalogMeasurementsResponse": {
         "type": "object",
         "description": "Latest per-song play counts for a catalog plus the valuation band derived from them",
+        "required": [
+          "status",
+          "measurements",
+          "valuation",
+          "total_streams",
+          "artist_account_id"
+        ],
         "properties": {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api-reference/openapi/releases.json` around lines 2469 - 2482, The response
schema for the valuation payload should explicitly require the echoed scope
fields instead of leaving them optional. Update the OpenAPI schema around
artist_account_id so it is listed in the relevant required array (with nullable:
true preserved), and apply the same treatment to any other core response fields
that clients must always receive; use the existing schema object definitions in
the valuation response to keep the contract enforceable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@api-reference/openapi/releases.json`:
- Around line 2469-2482: The response schema for the valuation payload should
explicitly require the echoed scope fields instead of leaving them optional.
Update the OpenAPI schema around artist_account_id so it is listed in the
relevant required array (with nullable: true preserved), and apply the same
treatment to any other core response fields that clients must always receive;
use the existing schema object definitions in the valuation response to keep the
contract enforceable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 109682cb-2c91-4476-9722-e887ca7f66a5

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf8704 and c3392f0.

📒 Files selected for processing (1)
  • api-reference/openapi/releases.json

@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

… caller-paginated rows (chat#1850)

Amendment per the 2026-07-07 decision on chat#1850: server-side
loop-to-exhaustion is rejected. The contract becomes:
- aggregates (measured_song_count, total_streams, valuation) computed in
  a single SQL aggregate over the latest-per-ISRC subquery — whole scope,
  no row cap, regardless of pagination
- the measurements array is caller-paginated: page/limit params (default
  limit 50, max 100), same pagination envelope as GET /api/catalogs/songs,
  rows sorted by play count descending
- artist_account_id filter + response echo unchanged; 400 also covers
  invalid page/limit

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.

Comment thread api-reference/openapi/releases.json Outdated
"/api/catalogs/measurements": {
"get": {
"description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song.",
"description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song. Optionally scope the read to one artist with artist_account_id: measurements and the valuation band then cover only the catalog's songs linked to that artist account. The read is always exhaustive - every matching measured song is returned and counted, with no row cap, regardless of catalog size.",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

KISS - keep param details out of the main endpoint description.

Suggested change
"description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song. Optionally scope the read to one artist with artist_account_id: measurements and the valuation band then cover only the catalog's songs linked to that artist account. The read is always exhaustive - every matching measured song is returned and counted, with no row cap, regardless of catalog size.",
"description": "Get the latest play counts and a derived valuation band for a catalog. Measurements are captured by [Create measurement job](/api-reference/research/measurement-jobs) runs; the band is computed at read time from the latest capture per song.",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Applied in 29231ea — the endpoint description is restored to the docs#265 original verbatim; the artist filter, page/limit, echo and no-cap semantics live on the query params and the response schema descriptions only.

…tails live on the params/schemas

Review feedback on docs#267: restore the short docs#265 endpoint
description; the artist filter, pagination, echo and no-cap semantics
are documented on the query params and response schema.

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.

…logId}/measurements

REST amendment on chat#1850: path for identity, query for modifiers —
mirrors the research measurements family
(/api/research/albums/{id}/measurements). catalogId is a required path
param; the query carries only the optional snake_case modifiers
(artist_account_id, page, limit). A missing id is unroutable (404);
a malformed uuid in the path is a 400.

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.

@sweetmantech sweetmantech merged commit a902d8e 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