Skip to content

metadata.service.ts: define track-context trust policy for LML search_type (compilation-accept) #1359

@jakebromberg

Description

@jakebromberg

Why now

BS#1355 introduces trustedAlbumMatch and migrates three librarian-typed call sites of LookupResponse.results[0].artwork to it. apps/backend/services/metadata/metadata.service.ts:72 is the one ungated consumer 1355 explicitly left alone, because it sits on a track-context surface (flowsheet now-playing) rather than an album-context surface, and the strict search_type === 'direct' gate would null out a cohort where current behavior is correct UX.

This ticket exists to decide that policy on its own timeline, not under regression pressure from a broader migration.

The site

// apps/backend/services/metadata/metadata.service.ts:68-77
const lookupResponse = await lmlLookupCoordinator.lookup(artistName, albumTitle, trackTitle, {
  caller: 'metadata-service',
  budgetMs: METADATA_SERVICE_LML_BUDGET_MS,
});
const artwork: DiscogsMatchResult | null = lookupResponse.results?.[0]?.artwork ?? null;

if (artwork) {
  result.album = extractAlbumMetadata(artwork);
  result.artist = extractArtistMetadata(artwork) ?? undefined;
}

Called fire-and-forget on every flowsheet track insert (enrichment.service.ts, flowsheet-metadata-backfill). Persists into flowsheet.discogs_release_id, flowsheet.artwork_url, flowsheet.release_year, four streaming URLs on the flowsheet row, plus album_metadata (via Epic D #898). Read by dj-site flowsheet rendering and iOS now-playing surfaces.

Why the BS#1355 helper doesn't fit

The flowsheet entry is a track played by a DJ, not a librarian's typed album. Walking through search_type values from that frame:

search_type LML meaning Track-context correctness
direct Discogs release matches the typed album Correct (all fields)
compilation Track appears on a compilation; release ≠ typed album Correct — artwork = compilation cover, streaming URL = compilation page that plays the typed track, release_year = compilation year. The track is there.
alternative Alternative release of the typed album (different country/format) Mostly correct — streaming URL plays the track, artwork is the alt release, release_year may diverge
fallback Artist-only match; album not confirmed Wrong-album (Yenbett-class)
song_as_artist Track title matched as artist field of an unrelated release Wrong-everything
none No match No data

direct and compilation produce data the now-playing surface should keep. fallback and song_as_artist and none produce Yenbett-class wrong-data. alternative is the edge.

trustedAlbumMatch (BS#1355) rejects everything except direct — correct for library.artwork_url (librarian typed a specific album) but a regression here for the compilation cohort.

Policy choices

A. Accept direct + compilation, reject the rest. Cleanest line. Compilation is the only non-direct type where LML explicitly confirms the track is on the release (driven by LML's SONG_AS_TRACK strategy, catalog-track-search plan §5.1). alternative confirms only the album shape, not the specific track. Add a third helper trustedTrackContextMatch to lml/trust.ts and migrate this site.

B. Accept direct + compilation + alternative, reject fallback + song_as_artist + none. Wider. The streaming-URL value of alternative (Spotify still plays the track from the alt release) is real, and the release_year discrepancy is a UX nit. Risk: alternative-release artwork showing up under a typed album occasionally misleads viewers.

C. Accept anything with a non-null track_match_hint. Read from the matched_via field on LookupResultItem (catalog-track-search §5.1) instead of search_type. Most precise but requires confirming LML populates matched_via on the relevant search-type branches.

D. Status quo + per-field policy. Keep accepting everything for streaming URLs (the "where to hear it" answer survives most match shapes), strict-gate only discogs_release_id and release_year (the "what is this release" claims). Splits the row, but matches what the data actually represents.

Recommendation

A. Mirrors the "specific track is confirmed on this release" axis cleanly, doesn't require touching matched_via shape, and the alternative cohort is small relative to compilation. D is the most accurate model of the data but the cost is non-trivial — split-helper API, two write paths through extractAlbumMetadata. Defer D unless A is shown to cut a meaningful real cohort.

Helper sketch for A:

/**
 * Track-context gate. Releases artwork when LML confirms the typed track
 * appears on the returned release. Use for flowsheet now-playing metadata
 * and similar track-played-by-DJ surfaces — NOT for librarian-typed album
 * surfaces (use trustedAlbumMatch for those).
 *
 * Accepts `direct` (release matches typed album) and `compilation` (track
 * confirmed on a compilation that is not the typed album, per LML's
 * SONG_AS_TRACK strategy). Rejects `alternative`, `fallback`,
 * `song_as_artist`, `none`.
 */
export function trustedTrackContextMatch(
  response: LookupResponse,
  opts: TrustOpts,
): DiscogsMatchResult | null { /* ... */ }

Out of scope

  • The artist-side fields (artist_bio, wikipedia_url) survive album-mismatch and don't need a track-context gate. Worth a follow-up helper if the artist cohort gets a different policy, but no current data-quality issue forces it.
  • Streaming-URL nuance per platform (Spotify/Apple/YT/Bandcamp/SoundCloud each have their own match strictness inside LML). Out of scope here; this ticket gates the BS-side consumption only.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestlmlTouches library-metadata-lookup

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions