Coverage for src / idx_api / models / vision_job.py: 83%
35 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
1"""Vision extraction job tracking model."""
3from datetime import datetime
4from typing import Optional
6from sqlalchemy import DateTime, Float, Integer, String, Text
7from sqlalchemy.orm import Mapped, mapped_column
9from idx_api.models.base import Base
12class VisionExtractionJob(Base):
13 """Track vision extraction job runs for monitoring and history.
15 Each row represents a single extraction job run, recording:
16 - Start/end times and duration
17 - Properties and images processed
18 - Success/failure counts
19 - Configuration used
20 """
22 __tablename__ = "vision_extraction_jobs"
24 id: Mapped[int] = mapped_column(primary_key=True)
26 # Job timing
27 started_at: Mapped[datetime] = mapped_column(
28 DateTime(timezone=True), nullable=False, default=datetime.utcnow
29 )
30 ended_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
31 duration_seconds: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
33 # Status: pending, running, completed, failed, cancelled
34 status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
36 # Scope
37 brokerage_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
38 limit_properties: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
40 # Progress counters
41 total_properties: Mapped[int] = mapped_column(Integer, default=0)
42 processed_properties: Mapped[int] = mapped_column(Integer, default=0)
43 total_images: Mapped[int] = mapped_column(Integer, default=0)
44 processed_images: Mapped[int] = mapped_column(Integer, default=0)
45 failed_images: Mapped[int] = mapped_column(Integer, default=0)
46 tags_created: Mapped[int] = mapped_column(Integer, default=0)
48 # Configuration snapshot
49 vision_model: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
50 skip_visual_embeddings: Mapped[bool] = mapped_column(default=False)
52 # Error tracking
53 last_error: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
54 error_count: Mapped[int] = mapped_column(Integer, default=0)
56 # Who triggered it
57 triggered_by: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
59 def __repr__(self) -> str:
60 return f"<VisionExtractionJob(id={self.id}, status='{self.status}', processed={self.processed_images}/{self.total_images})>"
62 @property
63 def progress_percent(self) -> float:
64 """Calculate progress percentage."""
65 if self.total_images == 0:
66 return 0.0
67 return (self.processed_images / self.total_images) * 100
69 @property
70 def images_per_second(self) -> float:
71 """Calculate processing rate."""
72 if not self.duration_seconds or self.duration_seconds == 0:
73 return 0.0
74 return self.processed_images / self.duration_seconds