fix: match bracketed guest credits and hyphenated version suffixes
Build and publish container / build (pull_request) Successful in 10m45s

Two normalisation faults, both found by running a real coverage report's
unmatched list back through the normaliser. Between them they account for five
of the fifteen worst misses by play count.

The guest-credit pattern required whitespace immediately before the word, so it
caught "Yellowcard feat. Tay Jardine" but missed "Self vs Self (feat. In
Flames)" -- and the bracketed form is the more common of the two. An opening
bracket is now allowed in that position.

The trailing-version pattern matched the suffix as a run of non-hyphens, which
cannot cross a hyphen inside the suffix itself: "Gold Dust - Shy FX Re-Edit" and
"Back To Your Roots - Friction & K-Tee Remix" both survived untouched. Matched
lazily instead.

Four version words are added for how drum and bass marks its variants: vip,
bootleg, rework, extended. They only apply inside a bracket or after a trailing
dash, so the exposure is small, and ordinary titles carrying those words --
Editors, Mixed Emotions, Radio Ga Ga, Live and Let Die -- are pinned as tests
against exactly that.

The report also gains the figures that explain why the MBID tier contributes so
little. Two thirds of scrobbles carry a recording id, and only a twentieth of
them join on one: MusicBrainz holds a separate recording per release, and the
two sides rarely choose the same one. Counting the pairs that carried an id and
matched on name anyway measures that disagreement directly, and settles that the
weakness is not a bug in the join.
This commit is contained in:
Emma Thorpe
2026-08-24 16:56:13 +01:00
parent edebecc8ea
commit 7588fee302
3 changed files with 94 additions and 7 deletions
+45
View File
@@ -687,3 +687,48 @@ def test_lidarr_talks_to_a_real_server_through_keep_alive(http_server):
client.transport.close()
assert server.connections == 1
# Titles taken verbatim from a real coverage report's unmatched list. Each one
# was a genuine miss before the normaliser handled it.
@pytest.mark.parametrize(
("scrobbled", "tagged"),
[
("Self vs Self (feat. In Flames)", "Self vs Self"),
("Grime Battle of Hastings (feat. The Town Crier)", "Grime Battle of Hastings"),
("Gold Dust - Shy FX Re-Edit", "Gold Dust"),
("Back To Your Roots - Friction & K-Tee Remix", "Back To Your Roots"),
("Constellations - Forza Horizon 3 VIP", "Constellations"),
("Everyday (Netsky Remix)", "Everyday"),
("Voodoo People [Pendulum Remix] [Live At Brixton Academy]", "Voodoo People"),
],
)
def test_real_unmatched_titles_now_agree_with_their_tags(scrobbled, tagged):
assert music_curator.normalise(scrobbled) == music_curator.normalise(tagged)
@pytest.mark.parametrize(
"title",
[
"Dancing with Myself",
"(Don't Fear) The Reaper",
"Live and Let Die",
"Radio Ga Ga",
"Editors",
"Mixed Emotions",
"Vipassana",
],
)
def test_the_version_words_do_not_eat_ordinary_titles(title):
"""Every one of these contains a version word and must survive intact."""
assert music_curator.normalise(title) == music_curator.normalise(title.lower())
assert len(music_curator.normalise(title).split()) == len(title.split())
def test_a_bracketed_guest_credit_matches_the_bare_tag(tmp_path):
"""Whitespace-then-feat misses the bracketed form, which is most of them."""
store = indexed(tmp_path, [scrobble_of("Yellowcard", "Here I Am Alive (feat. Someone)")])
method, track_id = verdict(store, "Yellowcard", "Here I Am Alive (feat. Someone)")
assert method == "name"
assert track_id is not None