"""tk_task state machine — pure logic (架构文档 §6.2 task 域, FR-TASK-01). 统一任务中心 is the only long-running-task view in the system; every long task (report / tryon / hairstyle / matting / render) moves through these states. """ from __future__ import annotations from enum import StrEnum from packages.domain.errors import InvalidTaskTransitionError class TaskStatus(StrEnum): PENDING = "pending" # created, quota reserved, not yet queued QUEUED = "queued" # handed to Celery RUNNING = "running" SUCCEEDED = "succeeded" FAILED = "failed" CANCELED = "canceled" class TaskType(StrEnum): REPORT = "report" TRYON = "tryon" HAIRSTYLE = "hairstyle" MATTING = "matting" RENDER = "render" PING = "ping" # scaffold-only: proves the async path end to end TERMINAL_STATUSES: frozenset[TaskStatus] = frozenset( {TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELED} ) #: Refunds are owed for every terminal state other than success (FR-TRY-05). REFUNDABLE_STATUSES: frozenset[TaskStatus] = frozenset({TaskStatus.FAILED, TaskStatus.CANCELED}) ALLOWED_TRANSITIONS: dict[TaskStatus, frozenset[TaskStatus]] = { TaskStatus.PENDING: frozenset({TaskStatus.QUEUED, TaskStatus.FAILED, TaskStatus.CANCELED}), TaskStatus.QUEUED: frozenset({TaskStatus.RUNNING, TaskStatus.FAILED, TaskStatus.CANCELED}), TaskStatus.RUNNING: frozenset( {TaskStatus.SUCCEEDED, TaskStatus.FAILED, TaskStatus.CANCELED, TaskStatus.QUEUED} ), TaskStatus.SUCCEEDED: frozenset(), TaskStatus.FAILED: frozenset({TaskStatus.QUEUED}), # retry re-queues TaskStatus.CANCELED: frozenset(), } def can_transition(current: TaskStatus, target: TaskStatus) -> bool: return target in ALLOWED_TRANSITIONS[current] def assert_transition(current: TaskStatus, target: TaskStatus) -> None: if not can_transition(current, target): raise InvalidTaskTransitionError(current=current.value, target=target.value) def is_terminal(status: TaskStatus) -> bool: return status in TERMINAL_STATUSES def needs_refund(status: TaskStatus) -> bool: return status in REFUNDABLE_STATUSES def progress_for(status: TaskStatus) -> int: return { TaskStatus.PENDING: 0, TaskStatus.QUEUED: 5, TaskStatus.RUNNING: 50, TaskStatus.SUCCEEDED: 100, TaskStatus.FAILED: 100, TaskStatus.CANCELED: 100, }[status]