Plugin: rework matching policy #35
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-29-matching-policy"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #29
Problem
The matcher collapsed duplicate candidates with
setdefault, stripped arbitrary punctuation, ignored duration, and did not honor or createapple_music_idassociations. Sync therefore could not persist an explicit ambiguous state.Changes
apple_music_id, unique ISRC, unique strong metadata, then unresolvedambiguous, persist resolution state and current playlist memberships inappleplaylists.dbapple_music_idon association, fill only an empty local ISRC, and never write audio-file tagsVerification
pytest -q— 80 passedgit diff --checkand Python compileall passedLibrary/Itemsmoke test confirmedapple_music_idand empty-ISRC persistenceScope
The interactive/explicit
matchcommand remains #31. Broader named/full-sync pruning and incomplete-view semantics remain #30.Review: changes recommended before merge
Reviewed
4d96ad6625against main. One correctness issue: the same Apple track can receive different matching decisions within a single sync, dropping earlier duplicate occurrences and making an unchanged second sync produce different contents. See inline reproduction.Validation: 74 tests passed; Ruff check passed for matching.py, sync.py and their test files; compileall and git diff --check passed. The additional reproduction used real beets Library/Item objects and temporary SQLite databases with a fake playlistmanager store. No source changes made.
This is a comment review because the reviewing account is also the PR author.
@ -67,0 +62,14 @@state_store.replace_memberships(apple_playlist.id, tracks)item_ids: list[int] = []for track in tracks:resolution = index.resolve(track)if resolution.item is None:state_store.set_resolution_state(track.library_track_id, resolution.state)result.missing_tracks.append(track)else:index.associate(resolution.item, track)state_store.set_resolution_state(track.library_track_id, resolution.state)P2: Use a consistent resolution for every occurrence of an Apple track.
index.associate()changes which candidates later calls toresolve()consider, but earlieritem_ids/missing_tracksentries are never reconciled. Reproduced with two real beets items having identical artist/title/duration and different ISRCs, and Apple occurrences[A (no ISRC), B (ISRC uniquely matching item 1), A]: the first A is ambiguous; B associates item 1; the second A now uniquely matches item 2. The first sync writes(1, 2)and reports A missing even though its persisted state is matched. An unchanged second sync writes(2, 1, 2). This loses one duplicate occurrence and makes the first result inconsistent with the saved association. Compute/reconcile decisions at Apple-track identity level before rendering occurrence lists, so all occurrences use the same final decision; add this regression case (and the equivalent across playlists).Additional independently identified and locally verified finding: the inclusive two-second duration boundary rejects some fractional-second values. This brings the review to two P2 correctness findings; changes recommended before merge.
@ -20,0 +40,4 @@item_duration = getattr(item, "length", None)if item_duration is None or track.duration_ms is None:return Truereturn abs(float(item_duration) * 1000 - track.duration_ms) <= 2000P2: Allow floating-point rounding at the inclusive duration boundary. Verified with a real beets
Item(length=128.003)and an otherwise identical Apple track withduration_ms=130003:abs(item.length * 1000 - track.duration_ms)is2000.0000000000146, and the unique metadata candidate resolves asunmatched. The intended difference is exactly two seconds, so this violates the inclusive tolerance and unnecessarily omits the track. Use a narrowly bounded numerical tolerance (without accepting genuinely over-limit durations) and test a fractional-second boundary as well as the existing whole-second case.Fixed both review findings in
ba04e3ca19(added to this PR without rewriting the original commit).[A, B, A]reproduction now yields(2, 1, 2)on both the first and second sync, with no contradictory missing report. Regression tests cover both one playlist and occurrences split across playlists, including persisted states and unchanged repeat-sync contents.Validation: 80 tests passed; focused Ruff lint/format checks, compileall and git diff --check passed. Both regressions were demonstrated failing before implementation. Independent review found no introduced correctness/security issues and independently ran all 80 tests. PR description updated with the fixes and current test count.