diff --git a/backend/app/api/downloads.py b/backend/app/api/downloads.py index fac7728..6cb980f 100644 --- a/backend/app/api/downloads.py +++ b/backend/app/api/downloads.py @@ -23,6 +23,7 @@ async def list_downloads(): status = request.args.get("status") from_date = request.args.get("from_date") to_date = request.args.get("to_date") + exclude_superseded = request.args.get("exclude_superseded", "").lower() in ("true", "1", "yes") page = int(request.args.get("page", 1)) per_page = min(int(request.args.get("per_page", 50)), 100) @@ -44,6 +45,9 @@ async def list_downloads(): filters.append(Download.created_at >= datetime.fromisoformat(from_date)) if to_date: filters.append(Download.created_at <= datetime.fromisoformat(to_date)) + # When filtering failed downloads, optionally exclude ones superseded by a later success + if exclude_superseded and status == DownloadStatus.FAILED: + filters.append(Download.superseded == False) if filters: query = query.where(and_(*filters)) @@ -161,15 +165,18 @@ async def recent_activity(): session = AsyncSession(bind=conn) # Get failed downloads OR completed with files (noteworthy activity) - # Eager load source and subscription for labeling - from sqlalchemy import or_ + # Exclude failed downloads that have been superseded by a later success + # for the same source - those are stale failures the user doesn't need to see query = select(Download).options( selectinload(Download.source).selectinload(Source.subscription) ).where( and_( Download.created_at >= cutoff, or_( - Download.status == DownloadStatus.FAILED, + and_( + Download.status == DownloadStatus.FAILED, + Download.superseded == False, + ), and_( Download.status == DownloadStatus.COMPLETED, Download.file_count > 0 @@ -306,12 +313,23 @@ async def get_stats(): recent_result = await session.execute(recent_query) recent_completed = recent_result.scalar() + # Active failed: failures not superseded by a later success for the same source + active_failed_query = select(func.count()).select_from(Download).where( + and_( + Download.status == DownloadStatus.FAILED, + Download.superseded == False, + ) + ) + active_failed_result = await session.execute(active_failed_query) + active_failed = active_failed_result.scalar() + return jsonify({ "total": sum(status_counts.values()), "by_status": status_counts, "by_platform": platform_counts, "completed": status_counts.get(DownloadStatus.COMPLETED, 0), "failed": status_counts.get(DownloadStatus.FAILED, 0), + "active_failed": active_failed, "pending": status_counts.get(DownloadStatus.PENDING, 0), "queued": status_counts.get(DownloadStatus.QUEUED, 0), "running": status_counts.get(DownloadStatus.RUNNING, 0),