fix: a second copy of a track counts once, joining words don't part two titles, and a newer matcher retries an older one's refusals (M502 #5569)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m18s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m33s
release / android (push) Successful in 5m47s
release / Build signed APK (releases and dev) (push) Successful in 6m4s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m18s
release / go (push) Successful in 1m35s
release / integration (push) Failing after 5m33s
release / android (push) Successful in 5m47s
release / Build signed APK (releases and dev) (push) Successful in 6m4s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
The second run's refusals were mostly albums holding a track twice (a two-track single with three files), which no edition can pair, and release names that differ only in "+" against "/". Tracks are now read once per disc, number and title, and "and", "&" and "+" count for nothing when titles are compared; an edition's own title also counts as the album's name. library_album_matches records which matcher version decided each album. Refusals by an older version are matched again, even after the listing is done, so an improvement reaches them without a migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -494,6 +494,7 @@ type LibraryAlbumMatch struct {
|
||||
ReleaseGroupMbid string
|
||||
Detail string
|
||||
DecidedAt pgtype.Timestamptz
|
||||
Matcher int32
|
||||
}
|
||||
|
||||
type LibraryChange struct {
|
||||
|
||||
@@ -20,6 +20,19 @@ func (q *Queries) ClearDefaultReleaseTypeProfile(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const countAlbumsToRematch = `-- name: CountAlbumsToRematch :one
|
||||
SELECT COUNT(*) FROM library_album_matches
|
||||
WHERE outcome = 'unmatched' AND matcher < $1::int
|
||||
`
|
||||
|
||||
// Refusals by an older matcher, which a finished listing goes back to.
|
||||
func (q *Queries) CountAlbumsToRematch(ctx context.Context, matcher int32) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countAlbumsToRematch, matcher)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const countWants = `-- name: CountWants :one
|
||||
SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted,
|
||||
COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing,
|
||||
@@ -126,9 +139,9 @@ func (q *Queries) DeleteWant(ctx context.Context, id pgtype.UUID) (int64, error)
|
||||
|
||||
const finishLibraryListing = `-- name: FinishLibraryListing :one
|
||||
UPDATE acquisition_settings
|
||||
SET library_listed_at = now(),
|
||||
SET library_listed_at = COALESCE(library_listed_at, now()),
|
||||
library_unlisted = (SELECT COUNT(*) FROM library_album_matches WHERE outcome = 'unmatched')
|
||||
WHERE id = true AND library_listed_at IS NULL
|
||||
WHERE id = true
|
||||
RETURNING library_listed_at, library_unlisted
|
||||
`
|
||||
|
||||
@@ -137,7 +150,8 @@ type FinishLibraryListingRow struct {
|
||||
LibraryUnlisted int32
|
||||
}
|
||||
|
||||
// The listing is done: count the albums no match was found for.
|
||||
// The listing is done, or done again after a newer matcher's retries:
|
||||
// count the albums no match was found for. The first finish is kept.
|
||||
func (q *Queries) FinishLibraryListing(ctx context.Context) (FinishLibraryListingRow, error) {
|
||||
row := q.db.QueryRow(ctx, finishLibraryListing)
|
||||
var i FinishLibraryListingRow
|
||||
@@ -405,13 +419,15 @@ SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.m
|
||||
JOIN artists ar ON ar.id = a.artist_id
|
||||
WHERE a.created_at <= $1::timestamptz
|
||||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m WHERE m.album_id = a.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
|
||||
WHERE m.album_id = a.id AND (m.outcome = 'listed' OR m.matcher >= $2::int))
|
||||
ORDER BY a.created_at, a.id
|
||||
LIMIT $2
|
||||
LIMIT $3
|
||||
`
|
||||
|
||||
type ListAlbumsToMatchParams struct {
|
||||
Cutoff pgtype.Timestamptz
|
||||
Matcher int32
|
||||
MaxRows int32
|
||||
}
|
||||
|
||||
@@ -424,11 +440,11 @@ type ListAlbumsToMatchRow struct {
|
||||
ArtistMbid *string
|
||||
}
|
||||
|
||||
// Library albums from before the cutoff that the listing has not decided:
|
||||
// each is matched to MusicBrainz (#5569). An album row with no tracks is
|
||||
// not an album in the library.
|
||||
// Library albums from before the cutoff that the listing has not decided,
|
||||
// or that an older matcher refused: each is matched to MusicBrainz (#5569).
|
||||
// An album row with no tracks is not an album in the library.
|
||||
func (q *Queries) ListAlbumsToMatch(ctx context.Context, arg ListAlbumsToMatchParams) ([]ListAlbumsToMatchRow, error) {
|
||||
rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.MaxRows)
|
||||
rows, err := q.db.Query(ctx, listAlbumsToMatch, arg.Cutoff, arg.Matcher, arg.MaxRows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -937,11 +953,12 @@ func (q *Queries) MarkGroupSeen(ctx context.Context, arg MarkGroupSeenParams) er
|
||||
}
|
||||
|
||||
const recordAlbumMatch = `-- name: RecordAlbumMatch :exec
|
||||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail, matcher)
|
||||
VALUES ($1, $2, $3, $4, $5,
|
||||
$6)
|
||||
ON CONFLICT (album_id) DO UPDATE
|
||||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method,
|
||||
release_group_mbid = EXCLUDED.release_group_mbid, detail = EXCLUDED.detail, decided_at = now()
|
||||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method, release_group_mbid = EXCLUDED.release_group_mbid,
|
||||
detail = EXCLUDED.detail, matcher = EXCLUDED.matcher, decided_at = now()
|
||||
`
|
||||
|
||||
type RecordAlbumMatchParams struct {
|
||||
@@ -950,6 +967,7 @@ type RecordAlbumMatchParams struct {
|
||||
Method string
|
||||
ReleaseGroupMbid string
|
||||
Detail string
|
||||
Matcher int32
|
||||
}
|
||||
|
||||
func (q *Queries) RecordAlbumMatch(ctx context.Context, arg RecordAlbumMatchParams) error {
|
||||
@@ -959,6 +977,7 @@ func (q *Queries) RecordAlbumMatch(ctx context.Context, arg RecordAlbumMatchPara
|
||||
arg.Method,
|
||||
arg.ReleaseGroupMbid,
|
||||
arg.Detail,
|
||||
arg.Matcher,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE library_album_matches DROP COLUMN matcher;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Which version of the matching (#5569) decided an album. A refusal by an
|
||||
-- older matcher is tried again by a newer one, so improving the matching
|
||||
-- reaches the albums it refused without a migration each time.
|
||||
ALTER TABLE library_album_matches ADD COLUMN matcher integer NOT NULL DEFAULT 0;
|
||||
@@ -272,18 +272,24 @@ SELECT a.id, 'listed', 'tags', a.release_group_mbid
|
||||
ON CONFLICT (album_id) DO NOTHING;
|
||||
|
||||
-- name: ListAlbumsToMatch :many
|
||||
-- Library albums from before the cutoff that the listing has not decided:
|
||||
-- each is matched to MusicBrainz (#5569). An album row with no tracks is
|
||||
-- not an album in the library.
|
||||
-- Library albums from before the cutoff that the listing has not decided,
|
||||
-- or that an older matcher refused: each is matched to MusicBrainz (#5569).
|
||||
-- An album row with no tracks is not an album in the library.
|
||||
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
|
||||
FROM albums a
|
||||
JOIN artists ar ON ar.id = a.artist_id
|
||||
WHERE a.created_at <= sqlc.arg(cutoff)::timestamptz
|
||||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m WHERE m.album_id = a.id)
|
||||
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
|
||||
WHERE m.album_id = a.id AND (m.outcome = 'listed' OR m.matcher >= sqlc.arg(matcher)::int))
|
||||
ORDER BY a.created_at, a.id
|
||||
LIMIT sqlc.arg(max_rows);
|
||||
|
||||
-- name: CountAlbumsToRematch :one
|
||||
-- Refusals by an older matcher, which a finished listing goes back to.
|
||||
SELECT COUNT(*) FROM library_album_matches
|
||||
WHERE outcome = 'unmatched' AND matcher < sqlc.arg(matcher)::int;
|
||||
|
||||
-- name: ListAlbumTracksToMatch :many
|
||||
-- What an album's tracks say about themselves, to check a match against.
|
||||
SELECT title, disc_number, track_number, duration_ms, mbid
|
||||
@@ -308,18 +314,20 @@ VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_nam
|
||||
ON CONFLICT (release_group_mbid) DO NOTHING;
|
||||
|
||||
-- name: RecordAlbumMatch :exec
|
||||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail)
|
||||
VALUES (sqlc.arg(album_id), sqlc.arg(outcome), sqlc.arg(method), sqlc.arg(release_group_mbid), sqlc.arg(detail))
|
||||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail, matcher)
|
||||
VALUES (sqlc.arg(album_id), sqlc.arg(outcome), sqlc.arg(method), sqlc.arg(release_group_mbid), sqlc.arg(detail),
|
||||
sqlc.arg(matcher))
|
||||
ON CONFLICT (album_id) DO UPDATE
|
||||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method,
|
||||
release_group_mbid = EXCLUDED.release_group_mbid, detail = EXCLUDED.detail, decided_at = now();
|
||||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method, release_group_mbid = EXCLUDED.release_group_mbid,
|
||||
detail = EXCLUDED.detail, matcher = EXCLUDED.matcher, decided_at = now();
|
||||
|
||||
-- name: FinishLibraryListing :one
|
||||
-- The listing is done: count the albums no match was found for.
|
||||
-- The listing is done, or done again after a newer matcher's retries:
|
||||
-- count the albums no match was found for. The first finish is kept.
|
||||
UPDATE acquisition_settings
|
||||
SET library_listed_at = now(),
|
||||
SET library_listed_at = COALESCE(library_listed_at, now()),
|
||||
library_unlisted = (SELECT COUNT(*) FROM library_album_matches WHERE outcome = 'unmatched')
|
||||
WHERE id = true AND library_listed_at IS NULL
|
||||
WHERE id = true
|
||||
RETURNING library_listed_at, library_unlisted;
|
||||
|
||||
-- name: ListUnmatchedLibraryAlbums :many
|
||||
|
||||
Reference in New Issue
Block a user