feat(valuation): POST /api/valuation — Spotify artist id → catalog + value band (chat#1867)
What
The consolidated valuation endpoint from chat#1867. One bearer-authed call turns a Spotify artist id into an account-owned catalog + estimated value band — implementing the docs#272 contract. It's the shared server-side version of marketing/lib/valuation/runValuationFlow, so marketing and chat call one endpoint instead of orchestrating client-side. It unblocks the chat onboarding "Claim your catalog" dead end for direct signups.
How — pure orchestration of existing internals
No new business logic, tables, or integrations — just composes what already exists:
generateAccessToken+getArtistAlbums→extractValuationAlbums(deduped album ids + earliest release date)createMeasurementJob { source:"current", scope:{ album_ids } }→ snapshot (spends the account's credits)waitForSnapshotMeasurements— bounded ~90s poll of the measurement store (value what lands, like marketing)createSnapshotCatalog— materialize the catalog from the snapshot (also attaches the canonical artist to the roster)selectCatalogMeasurementsAggregate+getCatalogEarliestReleaseDate+computeValuationBand— the same band model asGET /catalogs/{id}/measurements
The owning account is resolved from credentials (validateAuthContext), never the body. Route sets maxDuration = 120 for the capture wait.
Contract (matches docs#272)
200 { status, catalog, band, songs_measured } · 400 missing/invalid spotify_artist_id · 401 unauthorized · 402 insufficient credits (propagated from the measurement job) · 404 no releases for the artist / snapshot missing · 502 Spotify/resolve failure · 504 capture not ready in the wait window.
Tests (TDD red→green)
validateRunValuationBody3/3,extractValuationAlbums3/3,waitForSnapshotMeasurements2/2 (injected sleep so the poll is unit-tested without real waits). Fulllib/valuationsuite 8/8.tsc --noEmitadds 0 errors (repo baseline unchanged); eslint + prettier clean.
The orchestration handler composes already-tested internals; the end-to-end run (a live Spotify artist → real capture, ~90s, spends credits) is preview-verification pending — that's the gate before merge.
Merge order: docs#272 → this. Tracking: chat#1867.
Summary by cubic
Adds a consolidated POST /api/valuation endpoint that turns a Spotify artist id into an account-owned catalog and estimated value band in one bearer-authed call. Moves valuation orchestration server-side so marketing and chat share one endpoint; implements docs#272 and unblocks the chat onboarding “Claim your catalog” flow (chat#1867).
-
New Features
- Resolves artist releases from Spotify, dedupes album ids, captures earliest date; creates a measurement job and waits ~90s (
maxDuration = 120). - Materializes the catalog, computes the value band, and returns
{ status, catalog, band, songs_measured }; errors:400,401,402,404,502,504.
- Resolves artist releases from Spotify, dedupes album ids, captures earliest date; creates a measurement job and waits ~90s (
-
Refactors
- Consolidated auth + body validation into
validateRunValuationRequest(SRP); runs auth before body and returns{ accountId, spotify_artist_id }for the handler. - Clarified Spotify app-token vs caller auth; renamed
token->spotifyTokenand added inline comment. - Hardened tests:
waitForSnapshotMeasurementsasserts all 15 poll attempts on the false path;validateRunValuationRequestasserts the 400 error envelope and mocksvalidateAuthContext.
- Consolidated auth + body validation into