Model/domain/tasks/tasks.py
2026-05-19 16:35:09 +00:00

94 lines
2.9 KiB
Python

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