diff --git a/internal/coverart/artist_enricher.go b/internal/coverart/artist_enricher.go index 1771793a..0dc2f027 100644 --- a/internal/coverart/artist_enricher.go +++ b/internal/coverart/artist_enricher.go @@ -135,6 +135,12 @@ func (e *Enricher) recordArtistArt(ctx context.Context, artistID pgtype.UUID, th // EnrichArtistBatch drains up to limit artists whose eligibility loop // determines they need processing. Iterates serially. Returns // per-batch tallies for the orchestrator's scan-run record. +// +// Emits a single Info log line at end-of-batch with a category +// breakdown (succeeded / settled-none / no-MBID / left-NULL / +// errored). The JSON tally written to scan_runs keeps the existing +// processed/succeeded/failed shape for backwards compat with the +// admin overview UI; the log line is the operator's diagnostic. func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir string, progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) { if limit <= 0 { @@ -148,8 +154,10 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str if qerr != nil { return 0, 0, 0, fmt.Errorf("list missing artist art: %w", qerr) } + var settledNone, noMBID, leftNull, errored int for _, id := range rows { if ctx.Err() != nil { + e.logArtistBatchSummary(len(rows), processed, succeeded, settledNone, noMBID, leftNull, errored) return processed, succeeded, failed, ctx.Err() } processed++ @@ -157,6 +165,7 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str e.logger.Warn("coverart: artist batch entry failed", "artist_id", uuidString(id), "err", eerr) failed++ + errored++ if progressCb != nil { progressCb(processed, succeeded, failed) } @@ -166,25 +175,49 @@ func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir str row, rerr := q.GetArtistByID(ctx, id) if rerr != nil { failed++ + errored++ if progressCb != nil { progressCb(processed, succeeded, failed) } continue } - if row.ArtistArtSource != nil && + switch { + case row.ArtistArtSource != nil && *row.ArtistArtSource != "" && - *row.ArtistArtSource != "none" { + *row.ArtistArtSource != "none": succeeded++ - } else { + case row.ArtistArtSource != nil && *row.ArtistArtSource == "none": + settledNone++ + failed++ + case row.Mbid == nil || *row.Mbid == "": + noMBID++ + failed++ + default: + leftNull++ failed++ } if progressCb != nil { progressCb(processed, succeeded, failed) } } + e.logArtistBatchSummary(len(rows), processed, succeeded, settledNone, noMBID, leftNull, errored) return processed, succeeded, failed, nil } +// logArtistBatchSummary emits a single Info line at end-of-batch with +// the full category breakdown including MBID-skipped (artists eligible +// by the SQL filter but skipped inside EnrichArtist due to NULL MBID). +func (e *Enricher) logArtistBatchSummary(eligible, processed, succeeded, settledNone, noMBID, leftNull, errored int) { + e.logger.Info("coverart: artist enrichment batch complete", + "eligible", eligible, + "processed", processed, + "succeeded", succeeded, + "settled_none", settledNone, + "no_mbid", noMBID, + "left_null", leftNull, + "errored", errored) +} + // CleanupArtistArt removes the artist-art directory for a deleted // artist. Idempotent — no-op if the directory doesn't exist. Called // from the artist-delete cascade in tracks/service.go after the diff --git a/internal/coverart/enricher.go b/internal/coverart/enricher.go index d17940eb..ae9c7970 100644 --- a/internal/coverart/enricher.go +++ b/internal/coverart/enricher.go @@ -168,6 +168,12 @@ func (e *Enricher) recordAlbumCover(ctx context.Context, albumID pgtype.UUID, pa // EnrichBatch drains up to limit albums whose eligibility loop // determines they need processing. Iterates serially. Returns // per-batch tallies for the orchestrator's scan-run record. +// +// Emits a single Info log line at end-of-batch with a category +// breakdown (succeeded / settled-none / left-NULL / errored). The +// JSON tally written to scan_runs keeps the existing +// processed/succeeded/failed shape for backwards compat with the +// admin overview UI; the log line is the operator's diagnostic. func (e *Enricher) EnrichBatch(ctx context.Context, limit int, progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) { if limit <= 0 { @@ -181,8 +187,10 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int, if qerr != nil { return 0, 0, 0, fmt.Errorf("list missing: %w", qerr) } + var settledNone, leftNull, errored int for _, id := range rows { if ctx.Err() != nil { + e.logBatchSummary("album", len(rows), processed, succeeded, settledNone, leftNull, errored) return processed, succeeded, failed, ctx.Err() } processed++ @@ -190,6 +198,7 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int, e.logger.Warn("coverart: batch entry failed", "album_id", uuidString(id), "err", eerr) failed++ + errored++ if progressCb != nil { progressCb(processed, succeeded, failed) } @@ -199,27 +208,49 @@ func (e *Enricher) EnrichBatch(ctx context.Context, limit int, row, rerr := q.GetAlbumWithFirstTrackPath(ctx, id) if rerr != nil { failed++ + errored++ if progressCb != nil { progressCb(processed, succeeded, failed) } continue } - if row.CoverArtSource != nil && + switch { + case row.CoverArtSource != nil && (*row.CoverArtSource == "sidecar" || *row.CoverArtSource == "mbcaa" || *row.CoverArtSource == "embedded" || - *row.CoverArtSource == "theaudiodb") { + *row.CoverArtSource == "theaudiodb"): succeeded++ - } else { + case row.CoverArtSource != nil && *row.CoverArtSource == "none": + settledNone++ + failed++ + default: + leftNull++ failed++ } if progressCb != nil { progressCb(processed, succeeded, failed) } } + e.logBatchSummary("album", len(rows), processed, succeeded, settledNone, leftNull, errored) return processed, succeeded, failed, nil } +// logBatchSummary emits a single Info line at end-of-batch with the +// full category breakdown. Operator-side diagnostic; the +// processed/succeeded/failed tally collapses too much to debug +// "0 successes" symptoms by itself. +func (e *Enricher) logBatchSummary(stage string, eligible, processed, succeeded, settledNone, leftNull, errored int) { + e.logger.Info("coverart: enrichment batch complete", + "stage", stage, + "eligible", eligible, + "processed", processed, + "succeeded", succeeded, + "settled_none", settledNone, + "left_null", leftNull, + "errored", errored) +} + // EnrichRetryMissing drains both NULL and 'none' rows. Used by the // admin bulk-retry endpoint. Clears 'none' to NULL first so // EnrichAlbum picks them up.