soulsync/core/automation/handlers
Broque Thomas d861a40277 Personalized pipeline: refresh snapshot on first-run too
Reproduced: selecting Fresh Tape (or any kind never generated before)
and running the pipeline silently skipped — UI showed
"No tracks in Fresh Tape — skipping sync" with no clue why.

Root cause: ensure_playlist auto-creates the playlist row on first
access with `track_count=0` and `last_generated_at=NULL`, but
`is_stale=0` by default (the column default — fresh rows aren't
"stale", they're "never generated"). Pipeline only refreshed when
`is_stale=True` OR `refresh_first=True`, so first-run rows fell
through both branches → read the empty snapshot → skip.

Fix: pipeline now also refreshes when `existing.last_generated_at is
None`. Same control flow, one extra condition:

    if refresh_first OR is_stale OR last_generated_at is None:
        refresh
    else:
        read existing snapshot

This is the right signal: "has the generator ever run for this row"
is exactly what `last_generated_at` tracks (the column is set in
`_persist_snapshot` after every successful refresh).

Stubs in test_handlers_personalized_pipeline.py updated to expose
`last_generated_at` on their SimpleNamespace returns so the new
attribute read doesn't AttributeError. Fresh stubs get a non-None
timestamp so they're treated as already-generated; the new test
`test_never_generated_snapshot_triggers_first_refresh` pins the
first-run-forces-refresh behavior with `last_generated_at=None`.
2026-05-15 21:13:16 -07:00
..
__init__.py Personalized playlist pipeline: auto-sync discover-page playlists 2026-05-15 18:41:08 -07:00
_pipeline_shared.py Personalized playlist pipeline: auto-sync discover-page playlists 2026-05-15 18:41:08 -07:00
database_update.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
discover_playlist.py Extract automation handlers (2/N): playlist lifecycle group 2026-05-15 10:47:46 -07:00
download_cleanup.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
duplicate_cleaner.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
maintenance.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
personalized_pipeline.py Personalized pipeline: refresh snapshot on first-run too 2026-05-15 21:13:16 -07:00
playlist_pipeline.py Personalized playlist pipeline: auto-sync discover-page playlists 2026-05-15 18:41:08 -07:00
process_wishlist.py Extract automation handlers (1/N): infrastructure + 3 simple handlers 2026-05-15 10:25:41 -07:00
progress_callbacks.py Extract automation handlers (4/3 — finish): progress callbacks + scan-completion emitter 2026-05-15 11:59:32 -07:00
quality_scanner.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
refresh_mirrored.py Extract automation handlers (2/N): playlist lifecycle group 2026-05-15 10:47:46 -07:00
registration.py Personalized playlist pipeline: auto-sync discover-page playlists 2026-05-15 18:41:08 -07:00
run_script.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
scan_library.py Extract automation handlers (1/N): infrastructure + 3 simple handlers 2026-05-15 10:25:41 -07:00
scan_watchlist.py Extract automation handlers (1/N): infrastructure + 3 simple handlers 2026-05-15 10:25:41 -07:00
search_and_download.py Extract automation handlers (3/3): maintenance + misc, finishing the lift 2026-05-15 11:24:35 -07:00
sync_playlist.py Extract automation handlers (2/N): playlist lifecycle group 2026-05-15 10:47:46 -07:00