soulsync/webui/static
Broque Thomas a60546929e Fix Album Completeness job reporting zero findings for every album
Reported by sassmastawillis: the Album Completeness maintenance job
scans 3127 albums in 0.1 seconds and reports 0 findings — for every
user, regardless of whether their library is actually complete.
Restoring an older DB surfaced 7 correct findings, so the code logic
works; the DB state is what's making everything look complete.

Root cause: `albums.track_count` is only ever written by server-sync
paths — Plex's `leafCount`/`childCount` and SoulSync standalone's
`len(tracks)`. It's the OBSERVED count of tracks SoulSync has indexed,
which is always exactly what `COUNT(tracks)` returns for that album.
The completeness job treated it as the EXPECTED total and compared it
against the observed count. They're equal by construction, so
`actual >= expected` is always true: skip, 0.1s scan, 0 findings.

Fix: new `api_track_count INTEGER` column on `albums`, written only by
metadata-source code paths. Populated in two places so the scan is
fast and the fallback is robust.

1. Enrichment workers — shared helper `set_album_api_track_count`
   in `core/worker_utils.py`. Called by each worker's existing
   `_update_album` method alongside its other album-column UPDATEs:

   - spotify_worker: `album_obj.total_tracks` from the Spotify Album
     dataclass (already in hand, zero new API calls)
   - itunes_worker: same, from the iTunes Album dataclass
   - deezer_worker: `nb_tracks` from full_data, falling back to
     search_data when the full lookup didn't run
   - discogs_worker: count of tracklist rows where `type_=='track'`
     (Discogs tracklists interleave heading and index rows that
     shouldn't count as songs)

   Helper skips the write on zero/None/negative/non-numeric inputs
   so a source lacking track info can't clobber a good value a
   different source already wrote. Caller owns the transaction —
   helper just queues an UPDATE on the caller's cursor without
   committing, so it batches cleanly with each worker's existing
   multi-UPDATE pattern.

   Hydrabase worker deliberately not touched — it's a P2P mirror
   that doesn't write album metadata to the local DB. Hydrabase-
   primary users hit the fallback path below.

2. Album Completeness repair job — new `al.api_track_count` column
   in the SELECT, read first in the scan loop. On miss (album never
   enriched, or enrichment workers haven't run yet on a fresh
   install), falls through to the existing `_get_expected_total()`
   API lookup and persists the result via the same shared helper
   (wrapped in connection/commit management since the repair job
   runs outside a worker's batched transaction).

Also removed `al.track_count` from the scan's SELECT — now unused
since the observed count was the whole source of this bug, and
leaving a dead SELECT would invite a future engineer to re-introduce
the same comparison.

Help text on the job card was reworded so it honestly describes
current behavior ("counts cached during normal enrichment are used
when available; otherwise the job queries a metadata source
directly") rather than the old "active provider first, then others
as fallback" phrasing, which doesn't match how the cache actually
fills — any enrichment worker that runs can populate it, and the
last writer wins. Document-only follow-up if this edge case ever
bites in practice: add a `api_track_count_source` column so the
scan can prefer the configured primary source's count over others
(e.g. deluxe vs. standard edition mismatches). Not worth the
complexity today.

For existing users, the first completeness scan after upgrade is
fast to the extent their library is already enriched: the workers
already ran and populated `api_track_count` on their normal schedule.
For brand-new installs, the scan's fallback path handles the cold
start — slower, but correct, and subsequent scans are fast.

Does NOT affect:
- Download / post-processing / wishlist / sync code paths — none
  of them read `track_count` for completeness semantics.
- Plex / Jellyfin / Navidrome / standalone sync — still write
  `track_count` exactly as before; `api_track_count` is a separate
  column they never touch.
- Other repair jobs.
- Any UI path — same finding schema, just correct counts now.

Files:
- database/music_database.py — idempotent migration adding
  `api_track_count INTEGER DEFAULT NULL` to the existing album-column
  check block.
- core/worker_utils.py — new `set_album_api_track_count` helper with
  the documented skip-on-bad-input contract.
- core/spotify_worker.py, itunes_worker.py, deezer_worker.py,
  discogs_worker.py — one-liner call from each `_update_album`.
- core/repair_jobs/album_completeness.py — scan uses the cache;
  fallback path persists API-lookup results via the shared helper;
  help text updated to match actual behavior.
- tests/test_worker_utils_album_track_count.py — 9 tests covering
  the helper's write/skip contract + no-commit invariant.
- tests/test_album_completeness_job.py — 2 tests for the repair
  job's fallback-path wrapper.
- webui/static/helper.js — WHATS_NEW entry.

Credit: sassmastawillis spotted the bug; the "restored older DB
finds 7 albums" signal pinpointed DB state over code logic and
made the diagnosis tractable.
2026-04-24 12:39:41 -07:00
..
docs update docs page 2026-03-13 08:35:28 -07:00
vendor Add WebSocket real-time updates with automatic HTTP polling fallback 2026-03-03 18:26:29 -08:00
api-monitor.js Unify artist detail: route source artists to standalone page, retire inline Artists page 2026-04-22 17:00:52 -07:00
artists.png add static images 2026-03-09 17:35:46 -07:00
automation.png add static images 2026-03-09 17:35:46 -07:00
beatport-ui.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
core.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
dashboard.png add static images 2026-03-09 17:35:46 -07:00
discover.js Fix Discover hero 'View Discography' 404ing on source-only artists 2026-04-23 23:09:56 -07:00
discover.png add static images 2026-03-09 17:35:46 -07:00
docs-images-needed.md Service Badges, Page Headers, Docs Page, and Bug Fixe 2026-03-10 14:47:11 -07:00
docs.js Clean up stale Artists-page references in helper.js + docs.js 2026-04-22 20:29:32 -07:00
downloads.js MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries 2026-04-24 10:17:59 -07:00
enrichment.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
explorer.png Create explorer.png 2026-03-30 17:23:31 -07:00
favicon.png enlarge favicon 2026-02-13 16:11:32 -08:00
help.png Add media server setup, processing settings, text import, automation history, and streaming details 2026-03-10 15:35:18 -07:00
helper.js Fix Album Completeness job reporting zero findings for every album 2026-04-24 12:39:41 -07:00
hydrabase.png Add Hydrabase dev UI and WebSocket support 2026-02-21 00:32:04 -08:00
import.png add static images 2026-03-09 17:35:46 -07:00
init.js Address PR review feedback from JohnBaumb and Cin 2026-04-23 07:48:40 -07:00
library.js Library page empty state: offer to search metadata sources for the query 2026-04-23 17:28:16 -07:00
library.png add static images 2026-03-09 17:35:46 -07:00
media-player.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
mobile.css Drop Show/Hide Results button + auto-restore cached results on navigate-back 2026-04-23 22:11:49 -07:00
pages-extra.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
particles.js Add particle background toggle & optimize accent color caching 2026-03-13 16:05:22 -07:00
placeholder-album.png Add missing placeholder-album.png to stop 404 spam in console 2026-03-23 18:05:11 -07:00
search.js MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries 2026-04-24 10:17:59 -07:00
search.png add static images 2026-03-09 17:35:46 -07:00
settings.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
settings.png add static images 2026-03-09 17:35:46 -07:00
setup-wizard.css Add informative help text and tips to every setup wizard step 2026-04-11 23:26:01 -07:00
setup-wizard.js Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import 2026-04-18 17:35:20 -07:00
shared-helpers.js MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries 2026-04-24 10:17:59 -07:00
stats-automations.js playStatsTrack: fall back to streaming sources when not in library 2026-04-22 20:07:28 -07:00
style.css Fix global search popover not scrolling when results overflow 2026-04-24 08:34:03 -07:00
sync-services.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
sync-spotify.js Fix album track source propagation 2026-04-22 21:21:32 +03:00
sync.png add static images 2026-03-09 17:35:46 -07:00
trans.png Add SoulID worker with API-based debut year disambiguation 2026-03-21 10:21:06 -07:00
trans2.png Create trans2.png 2026-03-21 10:23:36 -07:00
whisoul.png Add library repair worker and UI 2026-02-21 15:00:23 -08:00
wishlist-tools.js Fix album track source propagation 2026-04-22 21:21:32 +03:00
worker-orbs.js Add Discogs to worker orbs animation and fix button styling 2026-04-02 17:18:31 -07:00