from dataclasses import dataclass from datetime import datetime, timezone from enum import Enum from typing import Optional from uuid import UUID, uuid4 from domain.tasks.subtasks import SubTaskStatus class TaskStatus(str, Enum): WAITING = "waiting" IN_PROGRESS = "in progress" COMPLETE = "complete" FAILED = "failed" class Source(str, Enum): PORTFOLIO = "portfolio_id" HUBSPOT_DEAL = "hubspot_deal_id" @dataclass class Task: id: UUID task_source: str status: TaskStatus = TaskStatus.WAITING service: Optional[str] = None source: Optional[Source] = None source_id: Optional[str] = None job_started: Optional[datetime] = None job_completed: Optional[datetime] = None @classmethod def create( cls, *, task_source: str, service: Optional[str] = None, source: Optional[Source] = None, source_id: Optional[str] = None, ) -> "Task": if not task_source.strip(): raise ValueError("task_source must be non-empty") return cls( id=uuid4(), task_source=task_source, service=service, source=source, source_id=source_id, status=TaskStatus.WAITING, job_started=datetime.now(timezone.utc), ) def start(self) -> None: if self.status not in (TaskStatus.WAITING, TaskStatus.IN_PROGRESS): raise ValueError(f"cannot start task in status {self.status}") if self.job_started is None: self.job_started = datetime.now(timezone.utc) self.status = TaskStatus.IN_PROGRESS def complete(self) -> None: self.status = TaskStatus.COMPLETE self.job_completed = datetime.now(timezone.utc) def fail(self) -> None: self.status = TaskStatus.FAILED self.job_completed = datetime.now(timezone.utc) def recalculate_from_subtasks(self, statuses: list[SubTaskStatus]) -> None: """Recompute Task.status from its SubTasks' statuses. Rule (preserved from legacy _update_task_progress): - any FAILED → FAILED - all COMPLETE → COMPLETE - any IN_PROGRESS → IN_PROGRESS - otherwise → WAITING Empty list is a no-op (newly-created task with no subtasks). """ if not statuses: return now = datetime.now(timezone.utc) if SubTaskStatus.FAILED in statuses: self.status = TaskStatus.FAILED self.job_completed = now elif all(s is SubTaskStatus.COMPLETE for s in statuses): self.status = TaskStatus.COMPLETE self.job_completed = now elif SubTaskStatus.IN_PROGRESS in statuses: self.status = TaskStatus.IN_PROGRESS self.job_completed = None else: self.status = TaskStatus.WAITING self.job_completed = None