_record_contributions now normalizes each source's top-N to [0,1] by its own
top score before summing across sources. ListenBrainz co-occurrence counts
(thousands) no longer drown Last.fm's 0–1 match scores, so a small-scale
source contributes real ranking signal instead of only adding candidates.
Discovery score assertions updated to the normalized scale (single-candidate
fakes normalize to 1.0); added a cross-source test showing a small-scale
source's candidate ranks equal to a large-scale one. min_score is now on the
[0, n_sources] scale (default 0.0 → unaffected). worker 231 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two row-leak bugs found in whole-branch review:
- discovery.run_discovery now prunes DiscoverySeedContribution rows whose
seedMbid is no longer a WatchedArtist (unfollow/removal has no FK cascade)
and recomputes the touched candidates' scores this sweep, instead of
letting a dead seed's contribution inflate the aggregate forever.
- maybe_run_scan's exception handler around scan_chunk now calls
clear_worklist and clears scan.id, matching the drain path. Previously
an exception mid-chunk left the already-done rows plus the remaining
worklist for that scan_id orphaned forever, since the next scan mints a
fresh uuid. Also clear scan.id in the build_worklist failure handler for
consistency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconciled the pre-existing test_config_from_config_parses_and_defaults
test in test_discovery.py, which referenced max_seeds/maxSeeds and would
otherwise have broken.
Adds registry.build_similarity_sources() (always constructs
ListenBrainzSource; reachability is a per-run health() concern, not a
startup gate) and wires discovery into the worker's main loop: a
scheduled sweep gated on discover.enabled + DISCOVER_TICK_SECONDS, and
a one-shot discover.requested trigger mirroring the existing
scan.requested pattern.
Also hardens run_discovery's health-check pass: a source whose
health() raises is now logged and skipped instead of aborting the
whole sweep (accepted finding from the Task-3 review).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>