soulsync/core/exports
BoulderBadgeDad 4743dfd644 playlist export: opt-in confident-search backfill for the unmatched tail (#945, increment 7)
Adds the third resolver stage for tracks the discovery cache + library can't resolve — a live
search of the target service, gated behind a "Match missing tracks" toggle so the API cost is opt-in.

The whole point is coverage WITHOUT the wrong-track risk, so it's a CONFIDENT match, not "search
and grab":
- search_service_track_id(artist, title, search_fn): searches the service, reranks via the existing
  relevance scorer (filter_and_rerank), and returns the top hit's id ONLY if it clears
  BACKFILL_MIN_SCORE (1.2 on the score_track scale). A wrong-artist hit (no 1.5x exact-artist boost,
  caps ~1.0) or a karaoke/cover (x0.05) can't clear the floor → None, and the track is left out
  rather than added wrong. search_fn injected → unit-testable without a live service.
- resolve_service_track_ids gains an optional search_id_fn: cache → library → search. Tallies
  from_search separately.
- _run_service_export builds the search fn from the service's metadata search client only when
  job['backfill'] is set; the endpoint reads `backfill` from the body; the modal adds the toggle and
  the status line shows "(N matched live)".

Store-back of confident matches deferred: a mirrored-only track may have no library row to write to,
so persisting needs the track→library mapping — a follow-up, not correctness.

9 new tests incl. the safety ones: wrong-artist rejected, karaoke/cover rejected, real-over-cover
picked, fail-safe on search error, and the cache→library→search waterfall + toggle wiring (on/off).
28 export/orchestration tests green, 64 script-integrity green, ruff clean.
2026-06-28 23:02:08 -07:00
..
__init__.py Watchlist: export the roster to JSON / CSV / text (corruption's request) 2026-06-11 22:48:58 -07:00
artist_export.py Library export: export the whole library roster too (corruption's request) 2026-06-11 22:59:21 -07:00
export_sources.py playlist export: opt-in confident-search backfill for the unmatched tail (#945, increment 7) 2026-06-28 23:02:08 -07:00
jspf_export.py #903: playlist export to ListenBrainz — pure cores (JSPF builder + MBID resolver) 2026-06-22 20:25:44 -07:00
mbid_resolver.py #903: playlist export to ListenBrainz — pure cores (JSPF builder + MBID resolver) 2026-06-22 20:25:44 -07:00
playlist_export.py playlist export: resolver foundation for Spotify/Deezer targets (#945, increment 1) 2026-06-28 21:04:47 -07:00
recording_mbid_cache.py #903: persistent recording-MBID cache + export orchestrator 2026-06-22 20:31:35 -07:00