Initial commit
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Callable
|
||||
from uuid import uuid4
|
||||
|
||||
from app.models import utcnow
|
||||
|
||||
|
||||
@dataclass
|
||||
class ImportJob:
|
||||
id: str
|
||||
inbox_id: str
|
||||
action: str
|
||||
status: str = "queued"
|
||||
scanned_messages: int = 0
|
||||
processed_messages: int = 0
|
||||
candidate_messages: int = 0
|
||||
valid_reports_imported: int = 0
|
||||
duplicate_messages_skipped: int = 0
|
||||
duplicate_reports_skipped: int = 0
|
||||
failed_messages: int = 0
|
||||
rejected_messages: int = 0
|
||||
records_imported: int = 0
|
||||
alerts_created: int = 0
|
||||
duplicate_report_samples: list[dict] | None = None
|
||||
error: str | None = None
|
||||
started_at: datetime = field(default_factory=utcnow)
|
||||
updated_at: datetime = field(default_factory=utcnow)
|
||||
completed_at: datetime | None = None
|
||||
|
||||
@property
|
||||
def progress_percent(self) -> int | None:
|
||||
if not self.scanned_messages:
|
||||
return None
|
||||
return min(100, round(self.processed_messages / self.scanned_messages * 100))
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
data = asdict(self)
|
||||
data["started_at"] = self.started_at.isoformat()
|
||||
data["updated_at"] = self.updated_at.isoformat()
|
||||
data["completed_at"] = self.completed_at.isoformat() if self.completed_at else None
|
||||
data["progress_percent"] = self.progress_percent
|
||||
data["duplicates_skipped"] = self.duplicate_messages_skipped + self.duplicate_reports_skipped
|
||||
return data
|
||||
|
||||
|
||||
class ImportJobStore:
|
||||
def __init__(self) -> None:
|
||||
self._jobs: dict[str, ImportJob] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def active_for_inbox(self, inbox_id: str) -> ImportJob | None:
|
||||
with self._lock:
|
||||
for job in sorted(self._jobs.values(), key=lambda item: item.started_at, reverse=True):
|
||||
if job.inbox_id == inbox_id and job.status in {"queued", "running"}:
|
||||
return job
|
||||
return None
|
||||
|
||||
def latest_for_inbox(self, inbox_id: str) -> ImportJob | None:
|
||||
with self._lock:
|
||||
jobs = [job for job in self._jobs.values() if job.inbox_id == inbox_id]
|
||||
return max(jobs, key=lambda item: item.started_at) if jobs else None
|
||||
|
||||
def create(self, inbox_id: str, action: str) -> ImportJob:
|
||||
job = ImportJob(id=uuid4().hex, inbox_id=inbox_id, action=action)
|
||||
with self._lock:
|
||||
self._jobs[job.id] = job
|
||||
return job
|
||||
|
||||
def get(self, job_id: str) -> ImportJob | None:
|
||||
with self._lock:
|
||||
return self._jobs.get(job_id)
|
||||
|
||||
def list(self, inbox_id: str | None = None) -> list[ImportJob]:
|
||||
with self._lock:
|
||||
jobs = list(self._jobs.values())
|
||||
if inbox_id:
|
||||
jobs = [job for job in jobs if job.inbox_id == inbox_id]
|
||||
return sorted(jobs, key=lambda item: item.started_at, reverse=True)
|
||||
|
||||
def update(self, job_id: str, mutator: Callable[[ImportJob], None]) -> ImportJob | None:
|
||||
with self._lock:
|
||||
job = self._jobs.get(job_id)
|
||||
if not job:
|
||||
return None
|
||||
mutator(job)
|
||||
job.updated_at = utcnow()
|
||||
return job
|
||||
|
||||
|
||||
import_jobs = ImportJobStore()
|
||||
Reference in New Issue
Block a user