soulsync/core/discovery
Broque Thomas 793593de51 Lift _run_tidal_discovery_worker to core/discovery/tidal.py
Missed worker from the PR5 discovery-workers series — Tidal sits in the
same domain as the deezer / spotify_public / listenbrainz / youtube /
beatport workers that were lifted in PR5b–PR5h, follows the same shape,
shares the same `_search_spotify_for_tidal_track` helper, and was simply
overlooked in the original inventory.

Pure 1:1 lift of the 212-line worker. Wrapper keeps the original
entry-point name so the existing call sites in web_server.py continue
to work without changes.

What `run_tidal_discovery_worker` does:

1. Pause enrichment workers (release shared resources).
2. For each Tidal track:
   - Cancellation gate (state['cancelled']).
   - Discovery cache lookup; cache hit short-circuits the search.
   - SimpleNamespace-style track passed straight to
     `_search_spotify_for_tidal_track` (the shared helper used by every
     worker in this family).
   - On Spotify match: build `match_data` preserving track_number /
     disc_number from raw API data, image extracted from album images
     or track object fallback, release_date filled from
     track.release_date when album dict is missing it.
   - On iTunes match: dict result populated as `match_data` with source
     set to discovery_source, image extracted from album images.
   - Save matched result to discovery cache.
   - On miss: Wing It stub stored as 'wing-it' status (success ticked).
3. After all tracks: phase='discovered', activity feed entry, sync
   discovery results back to mirrored playlist via
   `_sync_discovery_results_to_mirrored` with 'tidal' tag.
4. On error: state['phase']='error' + status with error string.
5. Finally: resume enrichment workers.

Dependencies injected via `TidalDiscoveryDeps` (13 fields) —
tidal_discovery_states, spotify_client, plus 11 callable helpers
(pause/resume enrichment, get_active_discovery_source,
get_metadata_fallback_client, get_discovery_cache_key, get_database,
validate_discovery_cache_artist, search_spotify_for_tidal_track,
build_discovery_wing_it_stub, add_activity_item,
sync_discovery_results_to_mirrored). Same surface as the deezer worker.

Diff vs original after `deps.X` → global X normalization is **zero
differences** — 212 lines orig = 212 lines lifted, byte-identical body
(including all whitespace, comments, log strings).

Tests: 9 new under tests/discovery/test_discovery_tidal.py covering
cache hit short-circuit, Spotify tuple match (track/disc preservation),
iTunes dict match path, Wing It fallback, cancellation, completion
phase update, activity feed entry, mirrored sync invocation, per-track
error handling.

Full suite: 1299 passing (was 1290). Ruff clean.
2026-04-28 23:02:24 -07:00
..
__init__.py PR5a: lift _run_sync_task to core/discovery/sync.py 2026-04-28 11:20:47 -07:00
beatport.py PR5f: lift _run_beatport_discovery_worker to core/discovery/beatport.py 2026-04-28 15:36:15 -07:00
deezer.py PR5d: lift _run_deezer_discovery_worker to core/discovery/deezer.py 2026-04-28 12:45:18 -07:00
listenbrainz.py PR5g: lift _run_listenbrainz_discovery_worker to core/discovery/listenbrainz.py 2026-04-28 16:09:02 -07:00
playlist.py PR5c: lift _run_playlist_discovery_worker to core/discovery/playlist.py 2026-04-28 12:20:48 -07:00
quality_scanner.py PR5h: lift _run_quality_scanner to core/discovery/quality_scanner.py 2026-04-28 18:41:29 -07:00
spotify_public.py PR5e: lift _run_spotify_public_discovery_worker to core/discovery/spotify_public.py 2026-04-28 13:13:41 -07:00
sync.py PR5a: lift _run_sync_task to core/discovery/sync.py 2026-04-28 11:20:47 -07:00
tidal.py Lift _run_tidal_discovery_worker to core/discovery/tidal.py 2026-04-28 23:02:24 -07:00
youtube.py fix: cast duration_ms to int before :02d format in discovery workers 2026-04-28 12:07:54 -07:00