"""GpuJob — a unit of GPU work the desktop agent pulls over HTTP (#114). The durable work list that lets the agent stay HTTP-only: the server enqueues a job per (image, task) — e.g. detect figures + CCIP-embed — and the agent LEASES a batch, computes on its GPU, then SUBMITS results, all over the already-exposed web API. Redis/Postgres stay private. A lease has an expiry; the lease query itself re-claims expired leases (agent died / stopped mid-batch), so the queue is self-healing without a separate sweep. One job is per ITEM; the agent fans a VIDEO out into per-frame instances internally (see image_region.frame_time). State: pending → leased → done | error (a failure under the attempt cap returns to pending for another agent). """ from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func from sqlalchemy.orm import Mapped, mapped_column from .base import Base class GpuJob(Base): __tablename__ = "gpu_job" id: Mapped[int] = mapped_column(Integer, primary_key=True) image_record_id: Mapped[int] = mapped_column( ForeignKey("image_record.id", ondelete="CASCADE"), index=True ) # What to compute, e.g. 'ccip' (detect figures + CCIP-embed) or 'siglip_region'. task: Mapped[str] = mapped_column(String(32), nullable=False) status: Mapped[str] = mapped_column( String(16), nullable=False, default="pending", index=True ) # pending | leased | done | error lease_token: Mapped[str | None] = mapped_column(String(64), nullable=True) leased_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) lease_expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0) error: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() )