fix(maintenance): drop dead status=="saved" scan

Watch later became a built-in playlist, so "saved" is no longer a VideoState
status — the scan matched nothing and under-counted users impacted by a video
removal. The playlist loop already covers Watch later, so impact is now correct.
Route the local _now through the shared utils.now_utc.
This commit is contained in:
npeter83 2026-06-26 03:15:36 +02:00
parent 7a97fef33c
commit 8ef748c7b8

View file

@ -20,18 +20,19 @@ Quota: phase 1 costs ~(flagged / 50) units, phase 2 ~(batch / 50). Both respect
backfill reserve as the other jobs so interactive syncs never starve.
"""
import logging
from datetime import datetime, timedelta, timezone
from datetime import timedelta
from sqlalchemy import or_, select
from sqlalchemy import select
from sqlalchemy.orm import Session
from app import progress, quota, sysconfig
from app.config import settings
from app.models import Playlist, PlaylistItem, Video, VideoState
from app.models import Playlist, PlaylistItem, Video
from app.notifications import create_notification
from app.state import get_maintenance_batch
from app.sync.runner import get_service_user
from app.sync.videos import apply_video_details, parse_dt
from app.utils import now_utc as _now
from app.youtube.client import YouTubeClient
log = logging.getLogger("siftlode.maintenance")
@ -45,10 +46,6 @@ GRACE_DAYS_BY_REASON = {
DEFAULT_GRACE_DAYS = settings.maintenance_grace_days
def _now() -> datetime:
return datetime.now(timezone.utc)
def _grace_days(reason: str | None) -> int:
return GRACE_DAYS_BY_REASON.get(reason or "", DEFAULT_GRACE_DAYS)
@ -91,14 +88,9 @@ def _collect_user_impact(db: Session, videos: list[Video]) -> dict[int, list[dic
impact: dict[int, set[str]] = {}
if not ids:
return {}
# Saved (VideoState.status == "saved").
for user_id, video_id in db.execute(
select(VideoState.user_id, VideoState.video_id).where(
VideoState.video_id.in_(ids), VideoState.status == "saved"
)
):
impact.setdefault(user_id, set()).add(video_id)
# In any of the user's playlists (covers watch-later, which is a playlist).
# Anyone who had it in a playlist — which now also covers "saved": Watch later became a
# built-in playlist, so "saved" is no longer a VideoState status (the old status=="saved"
# scan matched nothing and under-counted impacted users).
for user_id, video_id in db.execute(
select(Playlist.user_id, PlaylistItem.video_id)
.join(PlaylistItem, PlaylistItem.playlist_id == Playlist.id)