cadence-python-client 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cadence/__init__.py +18 -0
- cadence/_internal/__init__.py +8 -0
- cadence/_internal/activity/__init__.py +5 -0
- cadence/_internal/activity/_activity_executor.py +113 -0
- cadence/_internal/activity/_context.py +58 -0
- cadence/_internal/rpc/__init__.py +0 -0
- cadence/_internal/rpc/error.py +148 -0
- cadence/_internal/rpc/retry.py +104 -0
- cadence/_internal/rpc/yarpc.py +42 -0
- cadence/_internal/workflow/__init__.py +0 -0
- cadence/_internal/workflow/context.py +121 -0
- cadence/_internal/workflow/decision_events_iterator.py +161 -0
- cadence/_internal/workflow/decisions_helper.py +312 -0
- cadence/_internal/workflow/deterministic_event_loop.py +498 -0
- cadence/_internal/workflow/history_event_iterator.py +58 -0
- cadence/_internal/workflow/statemachine/__init__.py +0 -0
- cadence/_internal/workflow/statemachine/activity_state_machine.py +106 -0
- cadence/_internal/workflow/statemachine/decision_manager.py +157 -0
- cadence/_internal/workflow/statemachine/decision_state_machine.py +87 -0
- cadence/_internal/workflow/statemachine/event_dispatcher.py +76 -0
- cadence/_internal/workflow/statemachine/timer_state_machine.py +73 -0
- cadence/_internal/workflow/workflow_engine.py +245 -0
- cadence/_internal/workflow/workflow_intance.py +44 -0
- cadence/activity.py +255 -0
- cadence/api/v1/__init__.py +92 -0
- cadence/api/v1/common_pb2.py +90 -0
- cadence/api/v1/common_pb2.pyi +200 -0
- cadence/api/v1/common_pb2_grpc.py +24 -0
- cadence/api/v1/decision_pb2.py +67 -0
- cadence/api/v1/decision_pb2.pyi +225 -0
- cadence/api/v1/decision_pb2_grpc.py +24 -0
- cadence/api/v1/domain_pb2.py +68 -0
- cadence/api/v1/domain_pb2.pyi +145 -0
- cadence/api/v1/domain_pb2_grpc.py +24 -0
- cadence/api/v1/error_pb2.py +59 -0
- cadence/api/v1/error_pb2.pyi +82 -0
- cadence/api/v1/error_pb2_grpc.py +24 -0
- cadence/api/v1/history_pb2.py +134 -0
- cadence/api/v1/history_pb2.pyi +780 -0
- cadence/api/v1/history_pb2_grpc.py +24 -0
- cadence/api/v1/query_pb2.py +49 -0
- cadence/api/v1/query_pb2.pyi +59 -0
- cadence/api/v1/query_pb2_grpc.py +24 -0
- cadence/api/v1/service_domain_pb2.py +76 -0
- cadence/api/v1/service_domain_pb2.pyi +164 -0
- cadence/api/v1/service_domain_pb2_grpc.py +327 -0
- cadence/api/v1/service_meta_pb2.py +41 -0
- cadence/api/v1/service_meta_pb2.pyi +17 -0
- cadence/api/v1/service_meta_pb2_grpc.py +97 -0
- cadence/api/v1/service_visibility_pb2.py +71 -0
- cadence/api/v1/service_visibility_pb2.pyi +149 -0
- cadence/api/v1/service_visibility_pb2_grpc.py +362 -0
- cadence/api/v1/service_worker_pb2.py +116 -0
- cadence/api/v1/service_worker_pb2.pyi +350 -0
- cadence/api/v1/service_worker_pb2_grpc.py +743 -0
- cadence/api/v1/service_workflow_pb2.py +126 -0
- cadence/api/v1/service_workflow_pb2.pyi +395 -0
- cadence/api/v1/service_workflow_pb2_grpc.py +861 -0
- cadence/api/v1/tasklist_pb2.py +78 -0
- cadence/api/v1/tasklist_pb2.pyi +147 -0
- cadence/api/v1/tasklist_pb2_grpc.py +24 -0
- cadence/api/v1/visibility_pb2.py +47 -0
- cadence/api/v1/visibility_pb2.pyi +53 -0
- cadence/api/v1/visibility_pb2_grpc.py +24 -0
- cadence/api/v1/workflow_pb2.py +89 -0
- cadence/api/v1/workflow_pb2.pyi +365 -0
- cadence/api/v1/workflow_pb2_grpc.py +24 -0
- cadence/client.py +382 -0
- cadence/data_converter.py +78 -0
- cadence/error.py +111 -0
- cadence/metrics/__init__.py +12 -0
- cadence/metrics/constants.py +136 -0
- cadence/metrics/metrics.py +56 -0
- cadence/metrics/prometheus.py +165 -0
- cadence/sample/__init__.py +1 -0
- cadence/sample/client_example.py +15 -0
- cadence/sample/grpc_usage_example.py +230 -0
- cadence/sample/simple_usage_example.py +155 -0
- cadence/signal.py +174 -0
- cadence/worker/__init__.py +13 -0
- cadence/worker/_activity.py +60 -0
- cadence/worker/_base_task_handler.py +71 -0
- cadence/worker/_decision.py +62 -0
- cadence/worker/_decision_task_handler.py +285 -0
- cadence/worker/_poller.py +64 -0
- cadence/worker/_registry.py +245 -0
- cadence/worker/_types.py +26 -0
- cadence/worker/_worker.py +56 -0
- cadence/workflow.py +271 -0
- cadence_python_client-0.1.0.dist-info/METADATA +180 -0
- cadence_python_client-0.1.0.dist-info/RECORD +95 -0
- cadence_python_client-0.1.0.dist-info/WHEEL +5 -0
- cadence_python_client-0.1.0.dist-info/licenses/LICENSE +201 -0
- cadence_python_client-0.1.0.dist-info/licenses/NOTICE +19 -0
- cadence_python_client-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Decision Events Iterator for Cadence workflow orchestration.
|
|
4
|
+
|
|
5
|
+
This module provides functionality to iterate through workflow history events,
|
|
6
|
+
particularly focusing on decision-related events for replay and execution.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Iterator, List, Optional
|
|
11
|
+
|
|
12
|
+
from cadence._internal.workflow.history_event_iterator import HistoryEventsIterator
|
|
13
|
+
from cadence.api.v1.history_pb2 import HistoryEvent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DecisionEvents:
|
|
18
|
+
"""
|
|
19
|
+
Represents events for a single decision iteration.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
input: List[HistoryEvent]
|
|
23
|
+
output: List[HistoryEvent]
|
|
24
|
+
markers: List[HistoryEvent]
|
|
25
|
+
replay: bool
|
|
26
|
+
replay_current_time_milliseconds: int
|
|
27
|
+
next_decision_event_id: int
|
|
28
|
+
|
|
29
|
+
def get_output_event_by_id(self, event_id: int) -> Optional[HistoryEvent]:
|
|
30
|
+
for event in self.input:
|
|
31
|
+
if hasattr(event, "event_id") and event.event_id == event_id:
|
|
32
|
+
return event
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DecisionEventsIterator(Iterator[DecisionEvents]):
|
|
37
|
+
"""
|
|
38
|
+
Iterator for processing decision events from workflow history.
|
|
39
|
+
|
|
40
|
+
This is the main class that processes workflow history events and groups them
|
|
41
|
+
into decision iterations for proper workflow replay and execution.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
events: List[HistoryEvent],
|
|
47
|
+
):
|
|
48
|
+
self._events: HistoryEventsIterator = HistoryEventsIterator(events)
|
|
49
|
+
self._next_decision_event_id: Optional[int] = None
|
|
50
|
+
self._replay_current_time_milliseconds: Optional[int] = None
|
|
51
|
+
|
|
52
|
+
def __iter__(self):
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __next__(self) -> DecisionEvents:
|
|
56
|
+
"""
|
|
57
|
+
Process the next decision batch.
|
|
58
|
+
1. Find the next valid decision task started event during replay or last scheduled decision task events for non-replay
|
|
59
|
+
2. Collect the decision input events before the decision task
|
|
60
|
+
3. Collect the decision output events after the decision task
|
|
61
|
+
|
|
62
|
+
Relay mode is determined by checking if the decision task is completed or not
|
|
63
|
+
"""
|
|
64
|
+
decision_input_events: List[HistoryEvent] = []
|
|
65
|
+
decision_output_events: List[HistoryEvent] = []
|
|
66
|
+
decision_event: Optional[HistoryEvent] = None
|
|
67
|
+
for event in self._events:
|
|
68
|
+
match event.WhichOneof("attributes"):
|
|
69
|
+
case "decision_task_started_event_attributes":
|
|
70
|
+
next_event = self._events.peek()
|
|
71
|
+
|
|
72
|
+
# latest event, not replay, assign started event as decision event insteaad
|
|
73
|
+
if next_event is None:
|
|
74
|
+
decision_event = event
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
match next_event.WhichOneof("attributes"):
|
|
78
|
+
case (
|
|
79
|
+
"decision_task_failed_event_attributes"
|
|
80
|
+
| "decision_task_timed_out_event_attributes"
|
|
81
|
+
):
|
|
82
|
+
# skip failed / timed out decision tasks and continue searching
|
|
83
|
+
next(self._events)
|
|
84
|
+
continue
|
|
85
|
+
case "decision_task_completed_event_attributes":
|
|
86
|
+
# found decision task completed event, stop
|
|
87
|
+
decision_event = next(self._events)
|
|
88
|
+
break
|
|
89
|
+
case _:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"unexpected event type after decision task started event: {next_event}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
case _:
|
|
95
|
+
decision_input_events.append(event)
|
|
96
|
+
|
|
97
|
+
if not decision_event:
|
|
98
|
+
raise StopIteration("no decision event found")
|
|
99
|
+
|
|
100
|
+
# collect decision output events
|
|
101
|
+
while self._events.has_next():
|
|
102
|
+
nxt = self._events.peek() if self._events.has_next() else None
|
|
103
|
+
if nxt and not is_decision_event(nxt):
|
|
104
|
+
break
|
|
105
|
+
decision_output_events.append(next(self._events))
|
|
106
|
+
|
|
107
|
+
replay_current_time_milliseconds = decision_event.event_time.ToMilliseconds()
|
|
108
|
+
|
|
109
|
+
replay: bool
|
|
110
|
+
next_decision_event_id: int
|
|
111
|
+
if (
|
|
112
|
+
decision_event.WhichOneof("attributes")
|
|
113
|
+
== "decision_task_completed_event_attributes"
|
|
114
|
+
): # completed decision task
|
|
115
|
+
replay = True
|
|
116
|
+
next_decision_event_id = decision_event.event_id + 1
|
|
117
|
+
else:
|
|
118
|
+
replay = False
|
|
119
|
+
next_decision_event_id = decision_event.event_id + 2
|
|
120
|
+
|
|
121
|
+
# collect marker events
|
|
122
|
+
markers = [m for m in decision_output_events if is_marker_event(m)]
|
|
123
|
+
|
|
124
|
+
return DecisionEvents(
|
|
125
|
+
input=decision_input_events,
|
|
126
|
+
output=decision_output_events,
|
|
127
|
+
markers=markers,
|
|
128
|
+
replay=replay,
|
|
129
|
+
replay_current_time_milliseconds=replay_current_time_milliseconds,
|
|
130
|
+
next_decision_event_id=next_decision_event_id,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def is_decision_event(event: HistoryEvent) -> bool:
|
|
135
|
+
"""Check if an event is a decision output event."""
|
|
136
|
+
return event is not None and event.WhichOneof("attributes") in set(
|
|
137
|
+
[
|
|
138
|
+
"activity_task_scheduled_event_attributes",
|
|
139
|
+
"start_child_workflow_execution_initiated_event_attributes",
|
|
140
|
+
"timer_started_event_attributes",
|
|
141
|
+
"workflow_execution_completed_event_attributes",
|
|
142
|
+
"workflow_execution_failed_event_attributes",
|
|
143
|
+
"workflow_execution_canceled_event_attributes",
|
|
144
|
+
"workflow_execution_continued_as_new_event_attributes",
|
|
145
|
+
"activity_task_cancel_requested_event_attributes",
|
|
146
|
+
"request_cancel_activity_task_failed_event_attributes",
|
|
147
|
+
"timer_canceled_event_attributes",
|
|
148
|
+
"cancel_timer_failed_event_attributes",
|
|
149
|
+
"request_cancel_external_workflow_execution_initiated_event_attributes",
|
|
150
|
+
"marker_recorded_event_attributes",
|
|
151
|
+
"signal_external_workflow_execution_initiated_event_attributes",
|
|
152
|
+
"upsert_workflow_search_attributes_event_attributes",
|
|
153
|
+
]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def is_marker_event(event: HistoryEvent) -> bool:
|
|
158
|
+
return bool(
|
|
159
|
+
event is not None
|
|
160
|
+
and event.WhichOneof("attributes") == "marker_recorded_event_attributes"
|
|
161
|
+
)
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DecisionsHelper manages the next decision ID which is used for tracking decision state machines.
|
|
3
|
+
|
|
4
|
+
This helper ensures that decision IDs are properly assigned and tracked to maintain
|
|
5
|
+
consistency in the workflow execution state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict, Optional
|
|
11
|
+
|
|
12
|
+
from cadence._internal.workflow.statemachine.decision_state_machine import (
|
|
13
|
+
DecisionId,
|
|
14
|
+
DecisionType,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DecisionTracker:
|
|
22
|
+
"""Tracks a decision with its ID and current state."""
|
|
23
|
+
|
|
24
|
+
decision_id: DecisionId
|
|
25
|
+
scheduled_event_id: Optional[int] = None
|
|
26
|
+
initiated_event_id: Optional[int] = None
|
|
27
|
+
started_event_id: Optional[int] = None
|
|
28
|
+
is_completed: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DecisionsHelper:
|
|
32
|
+
"""
|
|
33
|
+
Helper class to manage decision IDs and work with DecisionManager state machines.
|
|
34
|
+
|
|
35
|
+
This class generates unique decision IDs and integrates with the DecisionManager
|
|
36
|
+
state machines for proper decision lifecycle tracking.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self):
|
|
40
|
+
"""
|
|
41
|
+
Initialize the DecisionsHelper with a DecisionManager reference.
|
|
42
|
+
"""
|
|
43
|
+
self._next_decision_counters: Dict[DecisionType, int] = {}
|
|
44
|
+
self._tracked_decisions: Dict[str, DecisionTracker] = {}
|
|
45
|
+
self._decision_id_to_key: Dict[str, str] = {}
|
|
46
|
+
logger.debug("DecisionsHelper initialized with DecisionManager integration")
|
|
47
|
+
|
|
48
|
+
def _get_next_counter(self, decision_type: DecisionType) -> int:
|
|
49
|
+
"""
|
|
50
|
+
Get the next counter value for a given decision type.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
decision_type: The type of decision
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The next counter value
|
|
57
|
+
"""
|
|
58
|
+
if decision_type not in self._next_decision_counters:
|
|
59
|
+
self._next_decision_counters[decision_type] = 1
|
|
60
|
+
else:
|
|
61
|
+
self._next_decision_counters[decision_type] += 1
|
|
62
|
+
|
|
63
|
+
return self._next_decision_counters[decision_type]
|
|
64
|
+
|
|
65
|
+
def generate_activity_id(self, activity_name: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Generate a unique activity ID.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
activity_name: The name of the activity
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A unique activity ID
|
|
74
|
+
"""
|
|
75
|
+
counter = self._get_next_counter(DecisionType.ACTIVITY)
|
|
76
|
+
activity_id = f"{activity_name}_{counter}"
|
|
77
|
+
|
|
78
|
+
# Track this decision
|
|
79
|
+
decision_id = DecisionId(DecisionType.ACTIVITY, activity_id)
|
|
80
|
+
tracker = DecisionTracker(decision_id)
|
|
81
|
+
self._tracked_decisions[activity_id] = tracker
|
|
82
|
+
self._decision_id_to_key[str(decision_id)] = activity_id
|
|
83
|
+
|
|
84
|
+
logger.debug(f"Generated activity ID: {activity_id}")
|
|
85
|
+
return activity_id
|
|
86
|
+
|
|
87
|
+
def generate_timer_id(self, timer_name: str = "timer") -> str:
|
|
88
|
+
"""
|
|
89
|
+
Generate a unique timer ID.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
timer_name: The name/prefix for the timer
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
A unique timer ID
|
|
96
|
+
"""
|
|
97
|
+
counter = self._get_next_counter(DecisionType.TIMER)
|
|
98
|
+
timer_id = f"{timer_name}_{counter}"
|
|
99
|
+
|
|
100
|
+
# Track this decision
|
|
101
|
+
decision_id = DecisionId(DecisionType.TIMER, timer_id)
|
|
102
|
+
tracker = DecisionTracker(decision_id)
|
|
103
|
+
self._tracked_decisions[timer_id] = tracker
|
|
104
|
+
self._decision_id_to_key[str(decision_id)] = timer_id
|
|
105
|
+
|
|
106
|
+
logger.debug(f"Generated timer ID: {timer_id}")
|
|
107
|
+
return timer_id
|
|
108
|
+
|
|
109
|
+
def generate_child_workflow_id(self, workflow_name: str) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Generate a unique child workflow ID.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
workflow_name: The name of the child workflow
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
A unique child workflow ID
|
|
118
|
+
"""
|
|
119
|
+
counter = self._get_next_counter(DecisionType.CHILD_WORKFLOW)
|
|
120
|
+
workflow_id = f"{workflow_name}_{counter}"
|
|
121
|
+
|
|
122
|
+
# Track this decision
|
|
123
|
+
decision_id = DecisionId(DecisionType.CHILD_WORKFLOW, workflow_id)
|
|
124
|
+
tracker = DecisionTracker(decision_id)
|
|
125
|
+
self._tracked_decisions[workflow_id] = tracker
|
|
126
|
+
self._decision_id_to_key[str(decision_id)] = workflow_id
|
|
127
|
+
|
|
128
|
+
logger.debug(f"Generated child workflow ID: {workflow_id}")
|
|
129
|
+
return workflow_id
|
|
130
|
+
|
|
131
|
+
def generate_marker_id(self, marker_name: str) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Generate a unique marker ID.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
marker_name: The name of the marker
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
A unique marker ID
|
|
140
|
+
"""
|
|
141
|
+
counter = self._get_next_counter(DecisionType.MARKER)
|
|
142
|
+
marker_id = f"{marker_name}_{counter}"
|
|
143
|
+
|
|
144
|
+
# Track this decision
|
|
145
|
+
decision_id = DecisionId(DecisionType.MARKER, marker_id)
|
|
146
|
+
tracker = DecisionTracker(decision_id)
|
|
147
|
+
self._tracked_decisions[marker_id] = tracker
|
|
148
|
+
self._decision_id_to_key[str(decision_id)] = marker_id
|
|
149
|
+
|
|
150
|
+
logger.debug(f"Generated marker ID: {marker_id}")
|
|
151
|
+
return marker_id
|
|
152
|
+
|
|
153
|
+
def get_decision_tracker(self, decision_key: str) -> Optional[DecisionTracker]:
|
|
154
|
+
"""
|
|
155
|
+
Get the decision tracker for a given decision key.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
decision_key: The decision key (activity_id, timer_id, etc.)
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The DecisionTracker if found, None otherwise
|
|
162
|
+
"""
|
|
163
|
+
return self._tracked_decisions.get(decision_key)
|
|
164
|
+
|
|
165
|
+
def update_decision_scheduled(
|
|
166
|
+
self, decision_key: str, scheduled_event_id: int
|
|
167
|
+
) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Update a decision tracker when it gets scheduled.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
decision_key: The decision key
|
|
173
|
+
scheduled_event_id: The event ID when the decision was scheduled
|
|
174
|
+
"""
|
|
175
|
+
tracker = self._tracked_decisions.get(decision_key)
|
|
176
|
+
if tracker:
|
|
177
|
+
tracker.scheduled_event_id = scheduled_event_id
|
|
178
|
+
logger.debug(
|
|
179
|
+
f"Updated decision {decision_key} with scheduled event ID {scheduled_event_id}"
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
logger.warning(f"No tracker found for decision key: {decision_key}")
|
|
183
|
+
|
|
184
|
+
def update_decision_initiated(
|
|
185
|
+
self, decision_key: str, initiated_event_id: int
|
|
186
|
+
) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Update a decision tracker when it gets initiated.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
decision_key: The decision key
|
|
192
|
+
initiated_event_id: The event ID when the decision was initiated
|
|
193
|
+
"""
|
|
194
|
+
tracker = self._tracked_decisions.get(decision_key)
|
|
195
|
+
if tracker:
|
|
196
|
+
tracker.initiated_event_id = initiated_event_id
|
|
197
|
+
logger.debug(
|
|
198
|
+
f"Updated decision {decision_key} with initiated event ID {initiated_event_id}"
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
logger.warning(f"No tracker found for decision key: {decision_key}")
|
|
202
|
+
|
|
203
|
+
def update_decision_started(self, decision_key: str, started_event_id: int) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Update a decision tracker when it gets started.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
decision_key: The decision key
|
|
209
|
+
started_event_id: The event ID when the decision was started
|
|
210
|
+
"""
|
|
211
|
+
tracker = self._tracked_decisions.get(decision_key)
|
|
212
|
+
if tracker:
|
|
213
|
+
tracker.started_event_id = started_event_id
|
|
214
|
+
logger.debug(
|
|
215
|
+
f"Updated decision {decision_key} with started event ID {started_event_id}"
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
logger.warning(f"No tracker found for decision key: {decision_key}")
|
|
219
|
+
|
|
220
|
+
def update_decision_completed(self, decision_key: str) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Mark a decision as completed.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
decision_key: The decision key
|
|
226
|
+
"""
|
|
227
|
+
tracker = self._tracked_decisions.get(decision_key)
|
|
228
|
+
if tracker:
|
|
229
|
+
tracker.is_completed = True
|
|
230
|
+
logger.debug(f"Marked decision {decision_key} as completed")
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(f"No tracker found for decision key: {decision_key}")
|
|
233
|
+
|
|
234
|
+
def _find_decision_by_scheduled_event_id(
|
|
235
|
+
self, scheduled_event_id: int
|
|
236
|
+
) -> Optional[str]:
|
|
237
|
+
"""Find a decision key by its scheduled event ID."""
|
|
238
|
+
for key, tracker in self._tracked_decisions.items():
|
|
239
|
+
if tracker.scheduled_event_id == scheduled_event_id:
|
|
240
|
+
return key
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def _find_decision_by_initiated_event_id(
|
|
244
|
+
self, initiated_event_id: int
|
|
245
|
+
) -> Optional[str]:
|
|
246
|
+
"""Find a decision key by its initiated event ID."""
|
|
247
|
+
for key, tracker in self._tracked_decisions.items():
|
|
248
|
+
if tracker.initiated_event_id == initiated_event_id:
|
|
249
|
+
return key
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
def _find_decision_by_started_event_id(
|
|
253
|
+
self, started_event_id: int
|
|
254
|
+
) -> Optional[str]:
|
|
255
|
+
"""Find a decision key by its started event ID."""
|
|
256
|
+
for key, tracker in self._tracked_decisions.items():
|
|
257
|
+
if tracker.started_event_id == started_event_id:
|
|
258
|
+
return key
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
def get_pending_decisions_count(self) -> int:
|
|
262
|
+
"""
|
|
263
|
+
Get the count of decisions that are not yet completed.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The number of pending decisions
|
|
267
|
+
"""
|
|
268
|
+
return sum(
|
|
269
|
+
1
|
|
270
|
+
for tracker in self._tracked_decisions.values()
|
|
271
|
+
if not tracker.is_completed
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def get_completed_decisions_count(self) -> int:
|
|
275
|
+
"""
|
|
276
|
+
Get the count of decisions that have been completed.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
The number of completed decisions
|
|
280
|
+
"""
|
|
281
|
+
return sum(
|
|
282
|
+
1 for tracker in self._tracked_decisions.values() if tracker.is_completed
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def reset(self) -> None:
|
|
286
|
+
"""Reset all decision tracking state."""
|
|
287
|
+
self._next_decision_counters.clear()
|
|
288
|
+
self._tracked_decisions.clear()
|
|
289
|
+
self._decision_id_to_key.clear()
|
|
290
|
+
logger.debug("DecisionsHelper reset")
|
|
291
|
+
|
|
292
|
+
def get_stats(self) -> Dict[str, int]:
|
|
293
|
+
"""
|
|
294
|
+
Get statistics about tracked decisions.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Dictionary with decision statistics
|
|
298
|
+
"""
|
|
299
|
+
stats = {
|
|
300
|
+
"total_decisions": len(self._tracked_decisions),
|
|
301
|
+
"pending_decisions": self.get_pending_decisions_count(),
|
|
302
|
+
"completed_decisions": self.get_completed_decisions_count(),
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# Add per-type counts
|
|
306
|
+
for decision_type in DecisionType:
|
|
307
|
+
type_name = decision_type.name.lower()
|
|
308
|
+
stats[f"{type_name}_count"] = self._next_decision_counters.get(
|
|
309
|
+
decision_type, 0
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
return stats
|