feat: play-count measurement store — identifiers, snapshots, measurements, ledger + queue

Merged
SW
4mo ago
+102-0

What

Five tables for the play-count measurement store (recoupable/chat#1791), built on songs.isrc as the canonical key — same FK pattern as catalog_songs/song_artists, written in the current lean migration style (no grant boilerplate, inline constraints, RLS enabled):

  • song_identifiers — external platform ID ↔ ISRC mapping (platform, identifier_type, value; unique reverse lookup). The Apify actor takes Spotify album URLs in and returns Spotify track IDs out, and songs holds no platform IDs today.
  • playcount_snapshots — snapshot jobs: mints the snapshot_id that POST /api/research/snapshots (docs#238) returns, tracks state/schedule/album_count/estimated_cost_usd; (account, created_at) index backs the per-org monthly cap (429).
  • song_measurements — append-only metric captures, the system of record. Unique (song, platform, metric, captured_at) (fetch-once), series index (song, platform, metric, captured_at DESC), CHECK (value >= 0), nullable snapshot FK for lineage. data_source matches the published contract field. No set_updated_at trigger: rows are never updated or deleted by application code.
  • songstats_quota_ledger — every Songstats hit recorded; sum(hits) over the rolling 30-day window enforces the ~1,000-hit budget. Nullable account FK + (account, spent_at) index attribute spend per org.
  • songstats_backfill_queue — value-ranked work list (rank_score = all-time count, CHECK >= 0), drain index (status, rank_score DESC) for FOR UPDATE SKIP LOCKED claims.

Review changes (0d741cb)

All review comments addressed: grants removed (KISS, per review), quota ledger gained account attribution (per review), cubic's two CHECK constraints applied, immutability comment reconciled. Branch merged up past database#31.

Merge order

Last database step of the #1791 train (cleanup ✅ → docs ✅ #238 → database → api). The api PRs depend on this (regenerated database.types.ts).

🤖 Generated with Claude Code