fix(artwork): partition the absent states instead of nesting them

ABSENT with FAILED beside it read as two buckets when the second was
inside the first, and "OF WHICH FAILED" fixed the ambiguity by admitting
it rather than removing it. The two columns are now disjoint and sum to
the absent total: NO IMAGE was answered, FAILED gave up.

The struct holds the same split the table prints, so the subtraction
happens once where the counts are collected.

The collectStatus fixture had two absent artists and no failure among
them, which made noImage and the absent total identical and left the
subtraction unverified — dropping it broke nothing. One of the two now
carries a last_failure.
This commit is contained in:
Deluan 2026-09-01 18:09:39 -04:00
parent 4535465cff
commit e92a90f5a9
2 changed files with 18 additions and 15 deletions

View File

@ -147,11 +147,11 @@ type sourceCount struct {
count int64
}
// absentCount partitions a kind's absent states: noImage was answered, failed gave up.
type absentCount struct {
kind model.Kind
count int64
// afterFailure is the subset that gave up rather than being told "no image".
afterFailure int64
kind model.Kind
noImage int64
failed int64
}
type statusReport struct {
@ -199,7 +199,7 @@ func collectStatus(ctx context.Context, ds model.DataStore) (statusReport, error
if err != nil {
return rep, fmt.Errorf("counting failed %s artwork: %w", k, err)
}
rep.absent = append(rep.absent, absentCount{kind: k, count: n, afterFailure: failed})
rep.absent = append(rep.absent, absentCount{kind: k, noImage: n - failed, failed: failed})
}
}
}
@ -229,12 +229,12 @@ func formatStatus(rep statusReport) string {
}
fmt.Fprintln(w, "\nAbsent (resolved, no image found)")
fmt.Fprintln(w, " KIND\tABSENT\tOF WHICH FAILED")
fmt.Fprintln(w, " KIND\tNO IMAGE\tFAILED")
for _, a := range rep.absent {
fmt.Fprintf(w, " %s\t%d\t%d\n", a.kind, a.count, a.afterFailure)
fmt.Fprintf(w, " %s\t%d\t%d\n", a.kind, a.noImage, a.failed)
}
fmt.Fprintln(w, " (nothing retries these; 'artwork reprocess --source absent' retries all of them)")
fmt.Fprintln(w, " (failed = gave up rather than being answered, so the subset most likely to resolve;\n"+
fmt.Fprintln(w, " (nothing retries these; 'artwork reprocess --source absent' retries both columns)")
fmt.Fprintln(w, " (failed = gave up rather than being answered, so the ones most likely to resolve;\n"+
" 'artwork reprocess --source failed' retries just those)")
fmt.Fprintln(w, "\nConfig")

View File

@ -816,7 +816,9 @@ var _ = Describe("collectStatus", func() {
}
put(model.KindArtistArtwork, "ar-1", "external:deezer", "h1", time.Now())
put(model.KindArtistArtwork, "ar-2", "", "", time.Now().Add(-24*time.Hour))
put(model.KindArtistArtwork, "ar-3", "", "", time.Now())
// ar-3 is absent because it gave up, so the two absent artists split across the columns.
Expect(art.PutItemArtwork(&model.ItemArtwork{ItemKind: "ar", ItemID: "ar-3",
ImageType: model.ImageTypePrimary, LastFailure: "[]", AttemptedAt: time.Now()})).To(Succeed())
put(model.KindAlbumArtwork, "al-1", "folder", "h2", time.Now())
Expect(queue.Enqueue(model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar-9",
ImageType: model.ImageTypePrimary, Priority: model.ArtworkPriorityBackfill})).To(Succeed())
@ -833,7 +835,8 @@ var _ = Describe("collectStatus", func() {
sourceCount{kind: model.KindArtistArtwork, source: "", count: 2},
sourceCount{kind: model.KindAlbumArtwork, source: "folder", count: 1},
))
Expect(rep.absent).To(ContainElement(absentCount{kind: model.KindArtistArtwork, count: 2}))
Expect(rep.absent).To(ContainElement(absentCount{kind: model.KindArtistArtwork, noImage: 1, failed: 1}),
"two absent artists, one answered and one that gave up")
})
It("compares the stored fingerprint against the current one", func() {
@ -867,7 +870,7 @@ var _ = Describe("formatStatus", func() {
{kind: model.KindArtistArtwork, source: "", count: 2},
},
absent: []absentCount{
{kind: model.KindArtistArtwork, count: 2, afterFailure: 1},
{kind: model.KindArtistArtwork, noImage: 1, failed: 1},
},
inputs: []artwork.FingerprintInput{{Name: "Agents", Value: "deezer,lastfm"}},
stored: "abc123",
@ -900,10 +903,10 @@ var _ = Describe("formatStatus", func() {
Expect(sources).To(MatchRegexp(`artist\s+absent\s+2`))
})
It("presents the failed count as a subset of the absent total, not a second bucket", func() {
It("partitions the absent states into answered and gave up", func() {
out := block(formatStatus(rep), "Absent (resolved, no image found)")
Expect(out).To(ContainSubstring("OF WHICH FAILED"))
Expect(out).To(MatchRegexp(`artist\s+2\s+1`), "2 absent, of which 1 failed")
Expect(out).To(ContainSubstring("NO IMAGE"))
Expect(out).To(MatchRegexp(`artist\s+1\s+1`), "1 answered plus 1 failed, summing to 2 absent")
Expect(formatStatus(rep)).To(ContainSubstring("artwork reprocess --source failed"))
})