import uuid from django.db import models from django.conf import settings class SplatJob(models.Model): class Status(models.TextChoices): QUEUED = "queued", "Queued" RUNNING = "running", "Running" SUCCEEDED = "succeeded", "Succeeded" FAILED = "failed", "Failed" class Step(models.TextChoices): EXTRACTING_FRAMES = "extracting_frames", "Extracting frames" RUNNING_COLMAP = "running_colmap", "Running COLMAP" TRAINING_GSPLAT = "training_gsplat", "Training gsplat" EXPORTING = "exporting", "Exporting .ksplat" QUALITY_CHECK = "quality_check", "Quality check" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) splat = models.OneToOneField("splats.Splat", on_delete=models.CASCADE, related_name="job") submitted_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name="splat_jobs" ) status = models.CharField(max_length=20, choices=Status.choices, default=Status.QUEUED, db_index=True) current_step = models.CharField(max_length=30, choices=Step.choices, blank=True, default="") # 0–100 overall progress, updated by the RunPod webhook on each step transition progress = models.PositiveSmallIntegerField(default=0) # RunPod serverless job ID — used to poll status and match incoming webhooks runpod_job_id = models.CharField(max_length=255, blank=True, default="", db_index=True) celery_task_id = models.CharField(max_length=255, blank=True, default="") # Number of times this job has been requeued after a transient failure retry_count = models.PositiveSmallIntegerField(default=0) error_message = models.TextField(blank=True, default="") # Structured log output from each pipeline step, keyed by Step value. # Populated by the RunPod webhook on each step completion. # Example: {"extracting_frames": {"frames": 1350, "duration_s": 8.2}, ...} pipeline_logs = models.JSONField(default=dict, blank=True) # COLMAP sparse reconstruction quality signal. # Low point count (< ~500) usually means the splat will be poor quality. colmap_points = models.PositiveIntegerField(null=True, blank=True) queued_at = models.DateTimeField(auto_now_add=True) started_at = models.DateTimeField(null=True, blank=True) finished_at = models.DateTimeField(null=True, blank=True) def __str__(self): return f"Job {self.id} for splat {self.splat_id} [{self.status}]" class Meta: db_table = "splat_jobs" indexes = [ models.Index(fields=["status", "queued_at"]), ]