feat(analytics): normalise API + contrat explicite get_next_action (Lot A)
Contrat get_next_action() — suppression du None ambigu :
{"status": "selected", "edge": ..., ...}
{"status": "terminal"}
{"status": "blocked", "reason": "no_valid_edge" | ...}
ExecutionLoop dispatche proprement : blocked -> PAUSED + _pause_requested,
terminal -> succès légitime. Rétrocompat défensive (None legacy -> blocked).
Analytics API normalisée (kwargs-only) :
on_execution_complete(duration_ms, status, steps_total|completed|failed)
on_step_complete(duration_ms, ...)
on_recovery_attempt(duration_ms, ...)
Découverte critique : les anciens appels utilisaient des méthodes et champs
inexistants (ExecutionMetrics.duration, metrics_collector.record_execution).
Le code n'avait jamais tourné au runtime — zéro analytics remontée.
L'exception était avalée par le try/except englobant.
58 tests (18 analytics + 11 contrat + 20 ExecutionLoop + 12 edge_scorer
non-régression). Migration complète, pas de pont legacy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,14 +96,16 @@ class TestWorkflowPipelineEnhanced:
|
||||
"confidence": 0.92
|
||||
}
|
||||
|
||||
# Mock de l'action suivante
|
||||
# Mock de l'action suivante (contrat dict normalisé Lot A)
|
||||
mock_workflow_pipeline.get_next_action.return_value = {
|
||||
"status": "selected",
|
||||
"edge_id": "edge_1",
|
||||
"action": {"type": "click", "target": "button"},
|
||||
"target_node": "node_2",
|
||||
"confidence": 0.95
|
||||
"confidence": 0.95,
|
||||
"score": 0.95,
|
||||
}
|
||||
|
||||
|
||||
# Mock du workflow
|
||||
mock_workflow = Mock(spec=Workflow)
|
||||
mock_edge = Mock(spec=WorkflowEdge)
|
||||
@@ -112,7 +114,7 @@ class TestWorkflowPipelineEnhanced:
|
||||
mock_edge.to_node = "node_2"
|
||||
mock_workflow.edges = [mock_edge]
|
||||
mock_workflow_pipeline.load_workflow.return_value = mock_workflow
|
||||
|
||||
|
||||
# Mock du résultat d'exécution
|
||||
mock_execution_result = Mock(spec=ExecutionResult)
|
||||
mock_execution_result.status = ExecutionStatus.SUCCESS
|
||||
@@ -121,24 +123,24 @@ class TestWorkflowPipelineEnhanced:
|
||||
mock_execution_result.target_resolved = None
|
||||
mock_execution_result.error = None
|
||||
mock_workflow_pipeline.action_executor.execute_edge.return_value = mock_execution_result
|
||||
|
||||
|
||||
# Créer l'instance enhanced
|
||||
enhanced = WorkflowPipelineEnhanced()
|
||||
|
||||
|
||||
# Lier les méthodes du pipeline mock
|
||||
enhanced.match_current_state = mock_workflow_pipeline.match_current_state
|
||||
enhanced.get_next_action = mock_workflow_pipeline.get_next_action
|
||||
enhanced.load_workflow = mock_workflow_pipeline.load_workflow
|
||||
enhanced.action_executor = mock_workflow_pipeline.action_executor
|
||||
enhanced.error_handler = mock_workflow_pipeline.error_handler
|
||||
|
||||
|
||||
# Act
|
||||
result = enhanced.execute_workflow_step_enhanced(
|
||||
workflow_id=workflow_id,
|
||||
current_state=mock_screen_state,
|
||||
context={"test_context": "value"}
|
||||
)
|
||||
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, WorkflowExecutionResult)
|
||||
assert result.success is True
|
||||
@@ -242,7 +244,8 @@ class TestWorkflowPipelineEnhanced:
|
||||
}
|
||||
|
||||
# Mock de l'action suivante (pas d'action = workflow terminé)
|
||||
mock_workflow_pipeline.get_next_action.return_value = None
|
||||
# Contrat dict normalisé Lot A : status="terminal" pour fin légitime
|
||||
mock_workflow_pipeline.get_next_action.return_value = {"status": "terminal"}
|
||||
|
||||
# Créer l'instance enhanced
|
||||
enhanced = WorkflowPipelineEnhanced()
|
||||
@@ -347,14 +350,16 @@ class TestWorkflowPipelineEnhanced:
|
||||
"confidence": 0.92
|
||||
}
|
||||
|
||||
# Mock de l'action suivante
|
||||
# Mock de l'action suivante (contrat dict normalisé Lot A)
|
||||
mock_workflow_pipeline.get_next_action.return_value = {
|
||||
"status": "selected",
|
||||
"edge_id": "edge_1",
|
||||
"action": {"type": "click", "target": "button"},
|
||||
"target_node": "node_2",
|
||||
"confidence": 0.95
|
||||
"confidence": 0.95,
|
||||
"score": 0.95,
|
||||
}
|
||||
|
||||
|
||||
# Mock du workflow
|
||||
mock_workflow = Mock(spec=Workflow)
|
||||
mock_edge = Mock(spec=WorkflowEdge)
|
||||
@@ -363,7 +368,7 @@ class TestWorkflowPipelineEnhanced:
|
||||
mock_edge.to_node = "node_2"
|
||||
mock_workflow.edges = [mock_edge]
|
||||
mock_workflow_pipeline.load_workflow.return_value = mock_workflow
|
||||
|
||||
|
||||
# Mock du résultat d'exécution
|
||||
mock_execution_result = Mock(spec=ExecutionResult)
|
||||
mock_execution_result.status = ExecutionStatus.SUCCESS
|
||||
@@ -372,17 +377,17 @@ class TestWorkflowPipelineEnhanced:
|
||||
mock_execution_result.target_resolved = None
|
||||
mock_execution_result.error = None
|
||||
mock_workflow_pipeline.action_executor.execute_edge.return_value = mock_execution_result
|
||||
|
||||
|
||||
# Créer l'instance enhanced
|
||||
enhanced = WorkflowPipelineEnhanced()
|
||||
|
||||
|
||||
# Lier les méthodes du pipeline mock
|
||||
enhanced.match_current_state = mock_workflow_pipeline.match_current_state
|
||||
enhanced.get_next_action = mock_workflow_pipeline.get_next_action
|
||||
enhanced.load_workflow = mock_workflow_pipeline.load_workflow
|
||||
enhanced.action_executor = mock_workflow_pipeline.action_executor
|
||||
enhanced.error_handler = mock_workflow_pipeline.error_handler
|
||||
|
||||
|
||||
# Act
|
||||
result = enhanced.execute_workflow_step_enhanced(
|
||||
workflow_id=workflow_id,
|
||||
|
||||
Reference in New Issue
Block a user