Skip to content

fix(autotag): "featuring" pattern in string_dist matches mid-word "ft" - #6843

Merged
snejus merged 4 commits into
beetbox:masterfrom
chuenchen309:fix/distance-featuring-pattern-mid-word-match
Jul 30, 2026
Merged

fix(autotag): "featuring" pattern in string_dist matches mid-word "ft"#6843
snejus merged 4 commits into
beetbox:masterfrom
chuenchen309:fix/distance-featuring-pattern-mid-word-match

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

Bug

SD_PATTERNS in beets/autotag/distance.py has this entry:

(r"[\[\(]?(featuring|feat|ft)[\. :].+", 0.1),

The ft alternative has no word boundary before it, so it matches ft embedded inside any ordinary word followed by a space/period/colon — draft, left, gift, craft, soft, swift, etc. — and treats everything after the match as a low-weight "featuring artist" suffix (weight 0.1), effectively deleting it from the comparison.

from beets.autotag.distance import string_dist
string_dist("Draft Beer", "Draft Whiskey")   # 0.05  (near-identical!)
string_dist("Left Field", "Left Symphony")   # 0.067
string_dist("Gift Ideas", "Gift Cards")      # 0.044

Confirmed through the actual matching pipeline (track_distance) too: "Draft Beer" vs. "Draft Whiskey" scores almost as close as an exact match, while a genuinely unrelated title correctly scores much higher. string_dist/add_string backs nearly every fuzzy field comparison used during beet import (title, artist, album, label, catalognum, ...), so this silently degrades match quality for any title/artist containing one of these words.

Fix

Add a word boundary before the alternation:

(r"[\[\(]?\b(featuring|feat|ft)[\. :].+", 0.1),

Verified this doesn't regress the intended "real" cases ("Song feat. Artist", "[feat: Artist]", "(ft. Artist)" — all still match, since they're preceded by whitespace/bracket/string-start, which are real boundaries) while excluding all the false-positive mid-word cases above.

Testing

Added test_featuring_pattern_does_not_match_mid_word (parametrized over draft/left/gift/craft) to test/autotag/test_distance.py, asserting string_dist(...) > 0.3 for these pairs (previously ~0.04-0.07). Verified red on master (git stash the source fix — all 4 cases fail with the near-zero distances shown above), green after. Full test/autotag/test_distance.py (58 passed) and the broader test/autotag/ + test/util/ + test/autotag/test_match.py + test/test_importer.py suites (352 + 142 passed, no regressions) all pass. ruff check/ruff format --check/mypy clean.

Added a changelog entry per CONTRIBUTING.rst's reminder.

Duplicate-work check

Searched open PRs for distance.py/"featuring" — found #6681 (Source class refactor) also touches distance.py, but its diff doesn't touch SD_PATTERNS or this area at all. No overlap.


This PR was drafted with AI assistance (Claude); I independently reproduced the bug against the real, unmodified module (including through the full track_distance matching pipeline) and verified the fix. I reviewed the change and take responsibility for it.

@chuenchen309
chuenchen309 requested a review from a team as a code owner July 16, 2026 00:13
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.63%. Comparing base (e808a3c) to head (b5019fb).
⚠️ Report is 5 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6843   +/-   ##
=======================================
  Coverage   75.62%   75.63%           
=======================================
  Files         163      163           
  Lines       21320    21320           
  Branches     3362     3362           
=======================================
+ Hits        16124    16126    +2     
+ Misses       4405     4404    -1     
+ Partials      791      790    -1     
Files with missing lines Coverage Δ
beets/autotag/distance.py 83.52% <ø> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread beets/autotag/distance.py Outdated
@chuenchen309

Copy link
Copy Markdown
Contributor Author

Applied — added the trailing \b so the boundary is symmetric on both sides. Full test/autotag/test_distance.py still passes (58/58), including the mid-word regression cases and the existing featuring-suffix matches. Thanks for the review!

@snejus snejus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@snejus
snejus enabled auto-merge July 16, 2026 12:13
@snejus
snejus force-pushed the fix/distance-featuring-pattern-mid-word-match branch from 83e39c0 to f0bb497 Compare July 16, 2026 12:14
chuenchen309 and others added 4 commits July 30, 2026 12:58
SD_PATTERNS' featuring/feat/ft regex has no word boundary before the
alternation, so it matches "ft" embedded in ordinary words like
"draft", "left", "gift", "craft" and treats everything after it as a
low-weight suffix. "Draft Beer" vs. "Draft Whiskey" scores 0.05
(near-identical) instead of correctly registering as very different.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply reviewer suggestion (@snejus): add a trailing \b after the
(featuring|feat|ft) alternation so the boundary is explicit on both
sides, matching the leading \b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@semohr
semohr force-pushed the fix/distance-featuring-pattern-mid-word-match branch from f0bb497 to b5019fb Compare July 30, 2026 10:58
@snejus
snejus merged commit 91c554b into beetbox:master Jul 30, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants