feat(server,web/m7-cover-sources): scan-orchestrator 4th stage

Adds an artist-art-enrich stage after cover-enrich in RunScan. The
stage drains EnrichArtistBatch with a configurable cap and the
operator's data_dir, persists processed/succeeded/failed tallies to
the scan_runs.artist_art_enrich jsonb column added in migration
0018.

The admin /admin Library Scan card grid expands from 3 to 4 columns
with the new "Artist art" card mirroring the cover-enrichment one
(Processed / Succeeded / Failed). The TS ScanStatus type gains an
optional artist_art_enrich field. The Go scanStatusResp gains the
matching ArtistArtEnrich json.RawMessage field.

cmd/minstrel/main.go (and admin scan-trigger handler) wire the new
RunScanConfig fields: ArtistEnrichCap reuses cfg.Library.CoverArtBackfillCap
for now (can be split if separate budgets are useful), DataDir is
cfg.Storage.DataDir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 13:03:36 -04:00
parent 295d9da361
commit 00cd28e79b
7 changed files with 108 additions and 26 deletions
+28 -2
View File
@@ -40,12 +40,22 @@ type CoverEnrichStageTallies struct {
Failed int `json:"failed"`
}
// ArtistArtEnrichStageTallies wires EnrichArtistBatch tallies into
// the scan_runs jsonb column.
type ArtistArtEnrichStageTallies struct {
Processed int `json:"processed"`
Succeeded int `json:"succeeded"`
Failed int `json:"failed"`
}
// RunScanConfig assembles the knobs for the orchestrator. BackfillCap and
// EnrichCap mirror the existing boot-time caps (5000 / coverArtBackfillCap)
// so manual triggers don't accidentally drain the whole library.
type RunScanConfig struct {
BackfillCap int
EnrichCap int
BackfillCap int
EnrichCap int
ArtistEnrichCap int // M7 cover-sources: cap for the artist-art enrich stage
DataDir string // M7 cover-sources: where to write artist-art files
}
// RunScan is the unified scan-chain orchestrator. Creates a scan_runs row,
@@ -131,6 +141,22 @@ func RunScan(
}
}
// Stage 4: artist art enrichment.
if enricher != nil && cfg.ArtistEnrichCap != 0 && cfg.DataDir != "" {
processed, succeeded, failed, ceerr := enricher.EnrichArtistBatch(ctx, cfg.ArtistEnrichCap, cfg.DataDir)
if ceerr != nil {
captureErr("artist_art_enrich", ceerr)
}
blob, _ := json.Marshal(ArtistArtEnrichStageTallies{
Processed: processed, Succeeded: succeeded, Failed: failed,
})
if uerr := q.UpdateScanRunArtistArtEnrich(ctx, dbq.UpdateScanRunArtistArtEnrichParams{
ID: row.ID, ArtistArtEnrich: blob,
}); uerr != nil {
captureErr("artist_art_enrich_persist", uerr)
}
}
// Finalize: set finished_at + error message (or empty string if all stages succeeded).
errMsg := ""
if firstErr != nil {