Make the artist a gate, not a quarter of the score

Requesting "Michael Jackson - Chicago" filed The Fall's "Chicago, Now!" from
Extricate. The chain: the artist name is filtered by the Soulseek server, so
the ladder correctly fell back to the bare title -- at which point the result
set is every song anyone has ever called Chicago, and the artist is the only
thing telling them apart.

match_ratio weights the artist at 0.25, so a perfect title with no artist
anywhere scores 0.75. That clears MIN_MATCH, and the ~22 point gap it leaves
against a genuine match is nothing next to the 340 points score() awards for a
free upload slot and a short queue. An idle peer holding the wrong song
therefore beat a busy peer holding the right one, every time.

That is the failure the confidence score was explicitly designed to prevent --
"a fast peer must not be able to raise the confidence of a wrong match" -- and
rewriting score() to rank availability first reintroduced it on the other side
of the wall. Availability is a tiebreak among plausible files; it is never a
reason to take a different song.

Candidates whose path names the artist are now kept outright and the rest
discarded, falling back to title-only matches only when there are none. The
downstream confidence gate still ran correctly here: it scored 0.75, said
"artist not found in path", and held the file for review.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Opus 5 committed 2026-09-02 14:10:34 -05:00
1 parent f0c5b5af00
commit 5b17d5fc8d
3 files changed
+85 -3

No files matched your search

+38
View File
@@ -481,6 +481,30 @@ def similarity(want: str, got: str) -> float:
return max(seq, len(a & b) / len(a) if a else 0.0)
def artist_confirmed(remote_path: str, artist: str) -> bool:
"""Is the requested artist actually named somewhere in this path?
This is a gate rather than a score because of what the query ladder does.
Once a filtered artist name forces the search down to the bare title, the
result set stops being "this song from various peers" and becomes "every
song anyone ever called Chicago" -- The Fall, Groove Armada, Sufjan
Stevens. The artist is then the only thing distinguishing the right
recording from a completely different song, and a weighted contribution is
not enough: 0.75 for a perfect title with no artist still clears MIN_MATCH,
and an available stranger will out-score a queued match every time.
"""
na = norm(artist)
if not na:
return False
path = norm(remote_path.replace("\\", "/"))
if na in path:
return True
# Every distinctive word, in any order: catches "Jackson, Michael" and
# "MJ - Chicago (Michael Jackson)" without matching on "the" or "and".
words = [w for w in na.split() if len(w) > 3]
return bool(words) and all(w in path for w in words)
def match_ratio(remote_path: str, artist: str, title: str) -> float:
"""How well a remote path matches the track asked for, 0..1.
@@ -1078,6 +1102,20 @@ async def pick_and_enqueue(job_id: str, artist: str, title: str,
set_state(job_id, "failed", no_candidate_detail(seen, stats, queries))
return
# Prefer candidates whose path actually names the artist, and only settle
# for ones that do not when there is nothing else. Availability is a
# tiebreak among plausible files, never a reason to take the wrong song.
named = {k: v for k, v in pool.items()
if artist_confirmed(v[2]["filename"], artist)}
if named:
if len(named) < len(pool):
log.info("job %s keeping %d/%d candidates that name %r",
job_id[:8], len(named), len(pool), artist)
pool = named
else:
log.info("job %s no candidate names %r; falling back to title-only "
"matches, which review will have to judge", job_id[:8], artist)
ranked = sorted(pool.values(), key=lambda r: -r[0])
cands = spread_by_peer(ranked)[:CANDIDATES]
with db() as c: