feat(enrichment): loose MusicBrainz search fallback (find aliased artists)

The MB text search used a strict field-phrase query
(`recording:"<title>" AND artist:"<artist>"`). A field phrase only matches
MusicBrainz's *primary* artist/title — it never searches ALIASES — so a
recording stored under a non-Latin primary name (大橋純子) whose romanized
form ("Junko Ohashi") is only an alias returns ZERO results, even though MB
has it. Whole swaths of a community library (e.g. romanized J-pop / city-pop
charts) were unsearchable.

- `build_recording_query(..., loose=True)` drops the field scoping + phrases
  for plain AND-ed term groups (`(telephone number) AND (junko ohashi)`),
  which searches the whole document incl. aliases.
- `_mb_search_recordings` runs the strict query first (unchanged, high
  precision) and only on an EMPTY result retries once with the loose query —
  so mainstream matches are untouched and the extra throttled request is spent
  only on a miss. Results are re-scored by rank_candidates, so recall goes up
  without lowering match quality (auto-accept still needs the per-field floors).

Verified live: "Junko Ohashi / Telephone Number" and "Anri / Windy Summer"
(both 0 under the strict query) now surface the real records; "AC/DC /
Highway to Hell" still hits strict at score 1.0 with no loose retry.

Follow-up (separate): alias-aware SCORING so these can auto-confirm, not just
appear as manual candidates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ChrisBeWithYou
2026-07-04 15:03:26 -05:00
co-authored by Claude Opus 4.8
parent b6169af6aa
commit 117d260723
4 changed files with 91 additions and 7 deletions
+15
View File
@@ -160,6 +160,21 @@ def test_build_recording_query_escapes_and_handles_missing_artist():
assert "artist:" not in q
def test_build_recording_query_loose_drops_field_phrases():
# The strict form (unchanged) locks to the *primary* artist/title phrase.
assert m.build_recording_query("Junko Ohashi", "Telephone Number") == \
'recording:"telephone number" AND artist:"junko ohashi"'
# The loose form has no field scoping and no phrases, so MusicBrainz also
# searches artist ALIASES — rescues non-Latin-primary artists (大橋純子).
loose = m.build_recording_query("Junko Ohashi", "Telephone Number", loose=True)
assert loose == "(telephone number) AND (junko ohashi)"
assert "artist:" not in loose and '"' not in loose
def test_build_recording_query_loose_missing_artist():
assert m.build_recording_query("", "Fantasy", loose=True) == "(fantasy)"
# ── MusicBrainz response parsing ──────────────────────────────────────────────
MB_DOC = {