feat(valuation): POST /api/valuation — Spotify artist id → catalog + value band (chat#1867)

Merged
SW
3mo ago
+351-0

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:

  1. generateAccessToken + getArtistAlbums → extractValuationAlbums (deduped album ids + earliest release date)
  2. createMeasurementJob { source:"current", scope:{ album_ids } } → snapshot (spends the account's credits)
  3. waitForSnapshotMeasurements — bounded ~90s poll of the measurement store (value what lands, like marketing)
  4. createSnapshotCatalog — materialize the catalog from the snapshot (also attaches the canonical artist to the roster)
  5. selectCatalogMeasurementsAggregate + getCatalogEarliestReleaseDate + computeValuationBand — the same band model as GET /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)

  • validateRunValuationBody 3/3, extractValuationAlbums 3/3, waitForSnapshotMeasurements 2/2 (injected sleep so the poll is unit-tested without real waits). Full lib/valuation suite 8/8.
  • tsc --noEmit adds 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.
  • 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 -> spotifyToken and added inline comment.
    • Hardened tests: waitForSnapshotMeasurements asserts all 15 poll attempts on the false path; validateRunValuationRequest asserts the 400 error envelope and mocks validateAuthContext.