Bin
2025-12-17 05a69820e0c402b0b33c063d3b922f0a0571cbbb
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 Annotation model.
 
This module defines declarative transitions for the Annotation entity.
Annotation transitions can update related task states via post_transition_hooks.
"""
 
from typing import Any, Dict, Optional
 
from fsm.registry import register_state_transition
from fsm.state_choices import AnnotationStateChoices
from fsm.transitions import ModelChangeTransition, StateModelType, TransitionContext
 
 
@register_state_transition('annotation', 'annotation_submitted', triggers_on_create=True, triggers_on_update=False)
class AnnotationSubmittedTransition(ModelChangeTransition):
    """
    Transition when an annotation is submitted.
 
    This is the default transition for newly created annotations.
 
    Trigger: Automatically on creation only (triggers_on_create=True, triggers_on_update=False)
    """
 
    def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
        return AnnotationStateChoices.SUBMITTED
 
    def get_reason(self, context: TransitionContext) -> str:
        """Return detailed reason for annotation submission."""
        return 'Annotation submitted for review'
 
    def transition(self, context: TransitionContext) -> Dict[str, Any]:
        """Execute annotation submission transition."""
        annotation = context.entity
 
        return {
            'task_id': annotation.task_id,
            'project_id': annotation.project_id,
            'completed_by_id': annotation.completed_by_id,
            'lead_time': annotation.lead_time,
        }
 
    def post_transition_hook(self, context: TransitionContext, state_record: StateModelType) -> None:
        """
        Post-transition hook for annotation submission.
 
        Updates task state to COMPLETED when annotation is submitted.
        Then updates project state based on task completion status.
        Handles "cold start" scenarios where task may not have state record yet.
        """
        from fsm.project_transitions import update_project_state_after_task_change
        from fsm.state_choices import TaskStateChoices
        from fsm.state_manager import StateManager
        from fsm.utils import get_or_initialize_state
 
        annotation = context.entity
        task = annotation.task
        project = annotation.project
 
        # Get current task state (initialize if needed)
        current_task_state = StateManager.get_current_state_value(task)
 
        if current_task_state is None:
            # Task has no state record - initialize it
            # Since annotation was just submitted, task should be COMPLETED
            current_task_state = get_or_initialize_state(
                task, user=context.current_user, inferred_state=TaskStateChoices.COMPLETED
            )
 
        # Transition task to COMPLETED if not already
        if current_task_state != TaskStateChoices.COMPLETED:
            StateManager.execute_transition(entity=task, transition_name='task_completed', user=context.current_user)
 
        # Update project state based on task changes
        update_project_state_after_task_change(project, user=context.current_user)
 
 
@register_state_transition(
    'annotation', 'annotation_updated', triggers_on_create=False, triggers_on_update=True, force_state_record=True
)
class AnnotationUpdatedTransition(ModelChangeTransition):
    """
    Transition when an annotation is updated.
 
    Updates keep the annotation in SUBMITTED state but create audit trail records.
 
    Trigger: On update (triggers_on_create=False, triggers_on_update=True, force_state_record=True)
    """
 
    def get_target_state(self, context: Optional[TransitionContext] = None) -> str:
        return AnnotationStateChoices.SUBMITTED
 
    def get_reason(self, context: TransitionContext) -> str:
        """Return detailed reason for annotation update."""
        return 'Annotation updated'
 
    def transition(self, context: TransitionContext) -> Dict[str, Any]:
        """Execute annotation update transition."""
        annotation = context.entity
 
        return {
            'task_id': annotation.task_id,
            'project_id': annotation.project_id,
            'updated_by_id': getattr(annotation, 'updated_by_id', None),
            'changed_fields': list(self.changed_fields.keys()) if self.changed_fields else [],
        }
 
    def post_transition_hook(self, context: TransitionContext, state_record: StateModelType) -> None:
        """Post-transition hook for annotation updates."""
        pass