feat: split unmatched listening by whether the library holds the title
Build and publish container / build (pull_request) Successful in 8m41s
Build and publish container / build (pull_request) Successful in 8m41s
"Unmatched by an artist the library holds" was presented as the matcher's misses. On real data it is not: owning one album by an artist says nothing about owning a particular single of theirs, and most of that figure turned out to be drum and bass tracks streamed but never bought. Split it in two. A title the library holds under some other artist is an attribution disagreement -- a remixer credited as the artist, a guest billed as one -- and is a genuine miss worth fixing; the report now names the artist the library files it under, which is the information needed to judge it. A title the library does not hold at all under any artist was never bought, and no improvement to matching will conjure it. The distinction matters beyond presentation. That figure is the gate on the cull, and a gate computed from a number that overstates the failure rate blocks work that is actually safe to do.
This commit is contained in:
@@ -732,3 +732,34 @@ def test_a_bracketed_guest_credit_matches_the_bare_tag(tmp_path):
|
||||
method, track_id = verdict(store, "Yellowcard", "Here I Am Alive (feat. Someone)")
|
||||
assert method == "name"
|
||||
assert track_id is not None
|
||||
|
||||
|
||||
def test_misses_are_split_by_whether_the_library_holds_the_title(tmp_path):
|
||||
"""Owning an artist is a weak proxy for owning a track. Counting both as
|
||||
matcher failures overstates the problem and would over-block the cull."""
|
||||
store = indexed(
|
||||
tmp_path,
|
||||
[
|
||||
# The library holds "Hells Bells", but under AC/DC, not Yellowcard:
|
||||
# an attribution disagreement, and a real miss.
|
||||
scrobble_of("Yellowcard", "Hells Bells"),
|
||||
# Yellowcard is in the library; this track is not, under any artist.
|
||||
scrobble_of("Yellowcard", "A Single She Never Bought"),
|
||||
],
|
||||
)
|
||||
|
||||
def count(extra):
|
||||
return store.connection.execute(
|
||||
"SELECT COUNT(*) FROM scrobble_key k WHERE k.track_id IS NULL"
|
||||
" AND EXISTS (SELECT 1 FROM lidarr_artist a WHERE a.norm_name = k.norm_artist)"
|
||||
f" {extra}"
|
||||
).fetchone()[0]
|
||||
|
||||
assert count("") == 2
|
||||
assert count("AND EXISTS (SELECT 1 FROM lidarr_track t WHERE t.norm_title = k.norm_track)") == 1
|
||||
|
||||
|
||||
def test_the_report_survives_the_attribution_split(tmp_path):
|
||||
store = indexed(tmp_path, [scrobble_of("Yellowcard", "Hells Bells")])
|
||||
|
||||
music_curator.report(store, NOW)
|
||||
|
||||
Reference in New Issue
Block a user