chenzhaoyang
2025-12-17 063da0bf961e1d35e25dc107f883f7492f4c5a7c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""
FSM Transitions for Task model.
 
This module defines declarative transitions for the Task entity.
 
Note: Most task state transitions (annotation_started, annotation_complete, completed)
are triggered by Annotation changes, not Task field changes. Those are handled by
annotation transitions via post_transition_hooks that update the parent task.
"""
 
from typing import Any, Dict, Optional
 
from fsm.registry import register_state_transition
from fsm.state_choices import TaskStateChoices
from fsm.transitions import ModelChangeTransition, TransitionContext
 
 
@register_state_transition('task', 'task_created', triggers_on_create=True, triggers_on_update=False)
class TaskCreatedTransition(ModelChangeTransition):
    """
    Transition when a new task is created.
 
    This is the initial state transition that occurs when a task is
    first saved to the database.
 
    Trigger: Automatically on creation (triggers_on_create=True)
 
    Note: Other task transitions (annotation_started, completed, etc.) are
    triggered by Annotation model changes, not Task field changes.
    """
 
    def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
        return TaskStateChoices.CREATED
 
    def get_reason(self, context: TransitionContext) -> str:
        """Return detailed reason for task creation."""
        return 'Task created in the system'
 
    def transition(self, context: TransitionContext) -> Dict[str, Any]:
        """
        Execute task creation transition.
 
        Args:
            context: Transition context containing task and user information
 
        Returns:
            Context data to store with the state record
        """
        task = context.entity
 
        return {
            'project_id': task.project_id,
            'data_keys': list(task.data.keys()) if task.data else [],
        }
 
 
# Note: Task state transitions (COMPLETED, IN_PROGRESS) are triggered by annotation changes
# via post_transition_hooks in annotation transitions, not by direct task model changes.
 
 
@register_state_transition('task', 'task_completed', triggers_on_create=False, triggers_on_update=False)
class TaskCompletedTransition(ModelChangeTransition):
    """
    Transition when task moves to COMPLETED state.
 
    Triggered when: First annotation is submitted on this task
    From: CREATED -> COMPLETED or IN_PROGRESS -> COMPLETED
    """
 
    def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
        return TaskStateChoices.COMPLETED
 
    def get_reason(self, context: TransitionContext) -> str:
        return 'Task completed - annotation submitted'
 
    def transition(self, context: TransitionContext) -> Dict[str, Any]:
        task = context.entity
        return {
            'task_id': task.id,
            'project_id': task.project_id,
            'total_annotations': task.total_annotations,
            'cancelled_annotations': task.cancelled_annotations,
            'is_labeled': task.is_labeled,
        }
 
 
@register_state_transition('task', 'task_in_progress', triggers_on_create=False, triggers_on_update=False)
class TaskInProgressTransition(ModelChangeTransition):
    """
    Transition when task moves to IN_PROGRESS state.
 
    Triggered when: All annotations are deleted from a completed task
    From: COMPLETED -> IN_PROGRESS
    """
 
    def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
        return TaskStateChoices.IN_PROGRESS
 
    def get_reason(self, context: TransitionContext) -> str:
        return 'Task moved to in progress - annotations deleted'
 
    def transition(self, context: TransitionContext) -> Dict[str, Any]:
        task = context.entity
        return {
            'task_id': task.id,
            'project_id': task.project_id,
            'total_annotations': task.total_annotations,
            'cancelled_annotations': task.cancelled_annotations,
            'is_labeled': task.is_labeled,
        }