Skip to content

feat(1493): export popularity field from album_popularity (Phase-2 Track 3)#1503

Merged
jakebromberg merged 2 commits into
mainfrom
feature/1493-export-popularity
Jun 29, 2026
Merged

feat(1493): export popularity field from album_popularity (Phase-2 Track 3)#1503
jakebromberg merged 2 commits into
mainfrom
feature/1493-export-popularity

Conversation

@jakebromberg

Copy link
Copy Markdown
Member

What

The final delivery step of BS#1486 Phase-2: the catalog export (GET /library/catalog) now ships a popularity field — the attribution-corrected, master-collapsed signal from the album_popularity table that Track 2 (#1492) built. Consumers (the deferred ranked-bundle seed in WXYC/wxyc-dj-ios#44) can rank releases by real popularity, while plays stays the honest per-pressing linked count.

How

apps/backend/services/catalog-export.service.ts:

  • popularity added to the private CatalogExportRow type + projectRow.
  • LEFT JOIN album_popularity on the row's logical key, derived by the same CASE the rebuild groups on: strip the discogs: namespace off canonical_entity_id (discogs:master:<id>master:<id>), keep any other non-null scheme verbatim, else library:<id>. Cross-referenced to album-popularity-refresh.service.ts (the writer); a divergence would silently null popularity, so the pg integration spec guards the join end-to-end.
  • Projected raw (::int, nullable), not COALESCE(...,0) like plays: null distinguishes "the logical album has no plays at all (linked or free-text)" from a genuine 0, per the merged SSOT contract (wxyc-shared#198).

Dependency

@wxyc/shared bumped ^1.13.0^1.15.0 — the release that published popularity to the SSOT CatalogExportRow. The parity drift-guard (catalog-export.parity.test.ts) reads the installed dist, so this bump is load-bearing (it goes red without it).

Tests

  • Parity guard passes (private type ↔ SSOT key sets agree at 15 fields).
  • Integration (pg): two pressings of one Discogs master collapse to the same popularity (validates the logical-key join); the multi-pressing logical album's popularity ≥ a single pressing's plays (the Catalog export popularity field + wiring (Phase-2 Track 3) #1493 acceptance criterion); a pressing with no plays of its own still shows the collapsed popularity; an unsignaled row ships popularity: null (raw, not 0).

Scope

No migration (reads the existing album_popularity). No new endpoint. iOS / Kotlin decoders pick up popularity on their next @wxyc/shared regen — additive + nullable, so existing decoders keep working.

Closes #1493

…ack 3)

Projects the attribution-corrected, master-collapsed `popularity` signal into the catalog export (`GET /library/catalog`) — the final delivery step of BS#1486 Phase-2. Track 2 (#1492) built the `album_popularity` table; this consumes it.

- catalog-export.service.ts: add `popularity` to the private `CatalogExportRow` type + `projectRow`, and LEFT JOIN `album_popularity` by the row's logical key — the `discogs:`-stripped `canonical_entity_id` (`discogs:master:<id>` -> `master:<id>`, verbatim for any other non-null scheme, else `library:<id>`), mirroring the CASE the rebuild groups on. Projected RAW (`::int`, nullable), NOT COALESCEd to 0 like `plays`: null means the logical album has no plays at all (linked or free-text), per the merged SSOT contract.
- Bump `@wxyc/shared` ^1.13.0 -> ^1.15.0 (the release that published `popularity` to the SSOT); the parity drift-guard reads the installed dist, so the bump is load-bearing.
- Integration: two pressings of one master collapse to the same popularity; the multi-pressing logical album's popularity >= a single pressing's plays; an unsignaled row ships raw null (not 0).
- app.yaml (Swagger display) kept consistent.

Closes #1493
…n branches

The serialize unit test hardcoded the 14-field contract and would have failed CI now that projectRow emits popularity as a 15th key; add popularity to the contract array, to the sampleRow fixture, and assert it round-trips raw-null (the field's distinguishing contract vs COALESCEd plays).

The integration spec only seeded discogs:master rows, so the reader's logical-key CASE was exercised on just the master branch even though the service comment claims "the pg integration spec guards it". Seed a discogs:release row and a NULL-canonical row each with a matching album_popularity entry so the release-form (substring) and library-fallback (ELSE) join-success branches are proven, not just the master form. NULL-canonical is the largest class in prod (~34k rows) and was previously only covered on the null-output path. Use probe id 7061 rather than 7060 to avoid colliding with library-query-sort-plays.spec.js under the shared-schema --runInBand pg suite.
@jakebromberg jakebromberg merged commit 4cf9b0d into main Jun 29, 2026
5 checks passed
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.

Catalog export popularity field + wiring (Phase-2 Track 3)

1 participant