diff --git a/backend/app/api/gpu.py b/backend/app/api/gpu.py index 09a488f..26d8377 100644 --- a/backend/app/api/gpu.py +++ b/backend/app/api/gpu.py @@ -96,6 +96,19 @@ async def backfill(): return jsonify({"celery_task_id": r.id, "task": task}), 202 +@gpu_bp.route("/reprocess", methods=["POST"]) +async def reprocess(): + """Reset every done/error job of `task` back to pending so the agent re-runs + the WHOLE library under the current pipeline (e.g. after adding crop + detectors). Heavy — the back-catalogue is otherwise skipped by the backfills.""" + body = await request.get_json(silent=True) or {} + task = str(body.get("task") or "ccip") + from ..tasks.ml import reprocess_gpu_jobs + + r = reprocess_gpu_jobs.delay(task) + return jsonify({"celery_task_id": r.id, "task": task}), 202 + + # --- Agent (bearer token): lease / submit / heartbeat / fail ------------ @gpu_bp.route("/jobs/lease", methods=["POST"]) diff --git a/backend/app/tasks/ml.py b/backend/app/tasks/ml.py index 84ca0ee..3501c64 100644 --- a/backend/app/tasks/ml.py +++ b/backend/app/tasks/ml.py @@ -549,6 +549,37 @@ def recover_orphaned_gpu_jobs() -> int: return res.rowcount or 0 +@celery.task(name="backend.app.tasks.ml.reprocess_gpu_jobs") +def reprocess_gpu_jobs(task_name: str = "ccip") -> int: + """Reset every done/error job of `task_name` back to pending so the agent + re-runs the WHOLE library under the CURRENT pipeline — e.g. after adding crop + detectors (#1202), re-cropping existing images. Heavy + operator-triggered; + the back-catalogue won't otherwise re-process (the backfills skip images that + already have current-version regions). Returns the number reset.""" + from datetime import UTC, datetime + + from sqlalchemy import update + + from ..models import GpuJob + + SessionLocal = _sync_session_factory() + with SessionLocal() as session: + now = datetime.now(UTC) + res = session.execute( + update(GpuJob) + .where( + GpuJob.task == task_name, + GpuJob.status.in_(["done", "error"]), + ) + .values( + status="pending", attempts=0, lease_token=None, leased_at=None, + lease_expires_at=None, updated_at=now, + ) + ) + session.commit() + return res.rowcount or 0 + + @celery.task( name="backend.app.tasks.ml.scheduled_ccip_auto_apply", soft_time_limit=1800, time_limit=2100, diff --git a/frontend/src/components/settings/GpuAgentCard.vue b/frontend/src/components/settings/GpuAgentCard.vue index b0da818..45f1638 100644 --- a/frontend/src/components/settings/GpuAgentCard.vue +++ b/frontend/src/components/settings/GpuAgentCard.vue @@ -71,6 +71,16 @@ images get these automatically; this catches the back-catalogue.
++ Re-runs the FULL pipeline (figure detection + CCIP + concept/panel crops) on + every image — use after changing crop detectors so the back-catalogue + gets re-cropped, not just new images. Heavy: re-processes the whole library. +
+