155 lines
3.9 KiB
Python
155 lines
3.9 KiB
Python
|
|
"""
|
||
|
|
POI Refresh Job Model
|
||
|
|
|
||
|
|
Tracks background jobs for periodic POI context refresh.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from sqlalchemy import Column, String, DateTime, Integer, Boolean, Text, Float
|
||
|
|
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
import uuid
|
||
|
|
|
||
|
|
from app.core.database import Base
|
||
|
|
|
||
|
|
|
||
|
|
class POIRefreshJob(Base):
|
||
|
|
"""
|
||
|
|
POI Refresh Background Job Model
|
||
|
|
|
||
|
|
Tracks periodic POI context refresh jobs for all tenants.
|
||
|
|
Jobs run on a configurable schedule (default: 180 days).
|
||
|
|
"""
|
||
|
|
|
||
|
|
__tablename__ = "poi_refresh_jobs"
|
||
|
|
|
||
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||
|
|
|
||
|
|
# Job scheduling
|
||
|
|
scheduled_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
index=True,
|
||
|
|
comment="When this job was scheduled"
|
||
|
|
)
|
||
|
|
started_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=True,
|
||
|
|
comment="When job execution started"
|
||
|
|
)
|
||
|
|
completed_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=True,
|
||
|
|
comment="When job execution completed"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Job status
|
||
|
|
status = Column(
|
||
|
|
String(50),
|
||
|
|
nullable=False,
|
||
|
|
default="pending",
|
||
|
|
index=True,
|
||
|
|
comment="Job status: pending, running, completed, failed"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Job execution details
|
||
|
|
attempt_count = Column(
|
||
|
|
Integer,
|
||
|
|
nullable=False,
|
||
|
|
default=0,
|
||
|
|
comment="Number of execution attempts"
|
||
|
|
)
|
||
|
|
max_attempts = Column(
|
||
|
|
Integer,
|
||
|
|
nullable=False,
|
||
|
|
default=3,
|
||
|
|
comment="Maximum number of retry attempts"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Location data (cached for job execution)
|
||
|
|
latitude = Column(
|
||
|
|
Float,
|
||
|
|
nullable=False,
|
||
|
|
comment="Bakery latitude for POI detection"
|
||
|
|
)
|
||
|
|
longitude = Column(
|
||
|
|
Float,
|
||
|
|
nullable=False,
|
||
|
|
comment="Bakery longitude for POI detection"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Results
|
||
|
|
pois_detected = Column(
|
||
|
|
Integer,
|
||
|
|
nullable=True,
|
||
|
|
comment="Number of POIs detected in this refresh"
|
||
|
|
)
|
||
|
|
changes_detected = Column(
|
||
|
|
Boolean,
|
||
|
|
default=False,
|
||
|
|
comment="Whether significant changes were detected"
|
||
|
|
)
|
||
|
|
change_summary = Column(
|
||
|
|
JSONB,
|
||
|
|
nullable=True,
|
||
|
|
comment="Summary of changes detected"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Error handling
|
||
|
|
error_message = Column(
|
||
|
|
Text,
|
||
|
|
nullable=True,
|
||
|
|
comment="Error message if job failed"
|
||
|
|
)
|
||
|
|
error_details = Column(
|
||
|
|
JSONB,
|
||
|
|
nullable=True,
|
||
|
|
comment="Detailed error information"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Next execution
|
||
|
|
next_scheduled_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=True,
|
||
|
|
index=True,
|
||
|
|
comment="When next refresh should be scheduled"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Metadata
|
||
|
|
created_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
default=lambda: datetime.now(timezone.utc)
|
||
|
|
)
|
||
|
|
updated_at = Column(
|
||
|
|
DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
default=lambda: datetime.now(timezone.utc),
|
||
|
|
onupdate=lambda: datetime.now(timezone.utc)
|
||
|
|
)
|
||
|
|
|
||
|
|
def __repr__(self):
|
||
|
|
return (
|
||
|
|
f"<POIRefreshJob(id={self.id}, tenant_id={self.tenant_id}, "
|
||
|
|
f"status={self.status}, scheduled_at={self.scheduled_at})>"
|
||
|
|
)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_overdue(self) -> bool:
|
||
|
|
"""Check if job is overdue for execution"""
|
||
|
|
if self.status in ("completed", "running"):
|
||
|
|
return False
|
||
|
|
return datetime.now(timezone.utc) > self.scheduled_at
|
||
|
|
|
||
|
|
@property
|
||
|
|
def can_retry(self) -> bool:
|
||
|
|
"""Check if job can be retried"""
|
||
|
|
return self.attempt_count < self.max_attempts
|
||
|
|
|
||
|
|
@property
|
||
|
|
def duration_seconds(self) -> float | None:
|
||
|
|
"""Calculate job duration in seconds"""
|
||
|
|
if self.started_at and self.completed_at:
|
||
|
|
return (self.completed_at - self.started_at).total_seconds()
|
||
|
|
return None
|