Add comprehensive COACHING mode system with: Backend: - core/coaching module with session persistence and metrics - CoachingSessionPersistence for pause/resume sessions - CoachingMetricsCollector with learning progress tracking - REST API blueprint for coaching sessions management - Execution integration with COACHING mode support Frontend: - CoachingPanel component with keyboard shortcuts - Decision buttons (accept/reject/correct/manual/skip) - Real-time stats display and correction editor - CorrectionPacksDashboard for pack visualization - WebSocket hooks for real-time COACHING events Metrics & Monitoring: - WorkflowLearningMetrics with confidence scoring - GlobalCoachingMetrics for system-wide analytics - AUTO mode readiness detection (85% acceptance threshold) - Learning progress levels (OBSERVATION → COACHING → AUTO) Tests: - E2E tests for complete OBSERVATION → AUTO journey - Session persistence and recovery tests - Metrics threshold validation tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
268 lines
7.4 KiB
Python
268 lines
7.4 KiB
Python
"""
|
|
Executions API Blueprint
|
|
|
|
Provides REST endpoints for workflow execution management.
|
|
|
|
Exigences: 6.1, 6.2, 6.3, 6.4
|
|
"""
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
from services.execution_integration import get_executor
|
|
|
|
executions_bp = Blueprint('executions', __name__)
|
|
|
|
|
|
@executions_bp.route('/', methods=['POST'])
|
|
def start_execution():
|
|
"""
|
|
Lance l'exécution d'un workflow.
|
|
|
|
Body JSON:
|
|
workflow_id: str - ID du workflow à exécuter
|
|
variables: dict (optionnel) - Variables d'entrée
|
|
mode: str (optionnel) - 'normal' ou 'coaching' (défaut: 'normal')
|
|
|
|
Returns:
|
|
execution_id: str - ID de l'exécution lancée
|
|
"""
|
|
data = request.get_json() or {}
|
|
workflow_id = data.get('workflow_id')
|
|
variables = data.get('variables', {})
|
|
mode = data.get('mode', 'normal')
|
|
|
|
if not workflow_id:
|
|
return jsonify({'error': 'workflow_id requis'}), 400
|
|
|
|
try:
|
|
executor = get_executor()
|
|
|
|
if mode == 'coaching':
|
|
execution_id = executor.execute_workflow_coaching(
|
|
workflow_id=workflow_id,
|
|
variables=variables
|
|
)
|
|
else:
|
|
execution_id = executor.execute_workflow(
|
|
workflow_id=workflow_id,
|
|
variables=variables
|
|
)
|
|
|
|
return jsonify({
|
|
'execution_id': execution_id,
|
|
'workflow_id': workflow_id,
|
|
'mode': mode,
|
|
'status': 'started'
|
|
}), 201
|
|
|
|
except ValueError as e:
|
|
return jsonify({'error': str(e)}), 404
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@executions_bp.route('/coaching', methods=['POST'])
|
|
def start_coaching_execution():
|
|
"""
|
|
Lance l'exécution d'un workflow en mode COACHING.
|
|
|
|
En mode COACHING, chaque étape est soumise à l'utilisateur pour
|
|
validation/correction avant exécution.
|
|
|
|
Body JSON:
|
|
workflow_id: str - ID du workflow à exécuter
|
|
variables: dict (optionnel) - Variables d'entrée
|
|
|
|
Returns:
|
|
execution_id: str - ID de l'exécution COACHING lancée
|
|
"""
|
|
data = request.get_json() or {}
|
|
workflow_id = data.get('workflow_id')
|
|
variables = data.get('variables', {})
|
|
|
|
if not workflow_id:
|
|
return jsonify({'error': 'workflow_id requis'}), 400
|
|
|
|
try:
|
|
executor = get_executor()
|
|
|
|
execution_id = executor.execute_workflow_coaching(
|
|
workflow_id=workflow_id,
|
|
variables=variables
|
|
)
|
|
|
|
return jsonify({
|
|
'execution_id': execution_id,
|
|
'workflow_id': workflow_id,
|
|
'mode': 'coaching',
|
|
'status': 'started',
|
|
'message': 'Connectez-vous via WebSocket pour recevoir les suggestions'
|
|
}), 201
|
|
|
|
except ValueError as e:
|
|
return jsonify({'error': str(e)}), 404
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@executions_bp.route('/', methods=['GET'])
|
|
def list_executions():
|
|
"""
|
|
Liste les exécutions.
|
|
|
|
Query params:
|
|
workflow_id: str (optionnel) - Filtrer par workflow
|
|
mode: str (optionnel) - Filtrer par mode ('normal', 'coaching')
|
|
|
|
Returns:
|
|
executions: list - Liste des exécutions
|
|
"""
|
|
workflow_id = request.args.get('workflow_id')
|
|
mode = request.args.get('mode')
|
|
|
|
executor = get_executor()
|
|
executions = executor.list_executions(workflow_id)
|
|
|
|
# Filtrer par mode si demandé
|
|
if mode:
|
|
if mode == 'coaching':
|
|
executions = [
|
|
e for e in executions
|
|
if executor.is_coaching_execution(e['execution_id'])
|
|
]
|
|
elif mode == 'normal':
|
|
executions = [
|
|
e for e in executions
|
|
if not executor.is_coaching_execution(e['execution_id'])
|
|
]
|
|
|
|
return jsonify({'executions': executions})
|
|
|
|
|
|
@executions_bp.route('/<execution_id>', methods=['GET'])
|
|
def get_execution(execution_id):
|
|
"""
|
|
Récupère le statut et les détails d'une exécution.
|
|
|
|
Returns:
|
|
Détails de l'exécution incluant statut, progression, logs
|
|
"""
|
|
executor = get_executor()
|
|
result = executor.get_execution_status(execution_id)
|
|
|
|
if result is None:
|
|
return jsonify({'error': f'Exécution {execution_id} introuvable'}), 404
|
|
|
|
response = result.to_dict()
|
|
response['is_coaching'] = executor.is_coaching_execution(execution_id)
|
|
|
|
return jsonify(response)
|
|
|
|
|
|
@executions_bp.route('/<execution_id>/cancel', methods=['POST'])
|
|
def cancel_execution(execution_id):
|
|
"""
|
|
Annule une exécution en cours.
|
|
|
|
Returns:
|
|
success: bool - Si l'annulation a réussi
|
|
"""
|
|
executor = get_executor()
|
|
|
|
if executor.cancel_execution(execution_id):
|
|
return jsonify({
|
|
'execution_id': execution_id,
|
|
'status': 'cancelled',
|
|
'message': 'Exécution annulée avec succès'
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'error': 'Impossible d\'annuler l\'exécution (déjà terminée ou inexistante)'
|
|
}), 400
|
|
|
|
|
|
@executions_bp.route('/<execution_id>/coaching/decision', methods=['POST'])
|
|
def submit_coaching_decision(execution_id):
|
|
"""
|
|
Soumet une décision COACHING pour une exécution.
|
|
|
|
Alternative REST à WebSocket pour soumettre une décision.
|
|
|
|
Body JSON:
|
|
decision: str - 'accept' | 'reject' | 'correct' | 'manual' | 'skip'
|
|
correction: dict (optionnel) - Correction si decision == 'correct'
|
|
feedback: str (optionnel) - Commentaire utilisateur
|
|
|
|
Returns:
|
|
success: bool
|
|
"""
|
|
data = request.get_json() or {}
|
|
decision = data.get('decision')
|
|
|
|
if not decision:
|
|
return jsonify({'error': 'decision requis'}), 400
|
|
|
|
valid_decisions = ['accept', 'reject', 'correct', 'manual', 'skip']
|
|
if decision not in valid_decisions:
|
|
return jsonify({
|
|
'error': f'decision invalide. Valeurs acceptées: {valid_decisions}'
|
|
}), 400
|
|
|
|
executor = get_executor()
|
|
|
|
if not executor.is_coaching_execution(execution_id):
|
|
return jsonify({
|
|
'error': f'{execution_id} n\'est pas une exécution COACHING'
|
|
}), 400
|
|
|
|
decision_response = {
|
|
'decision': decision,
|
|
'correction': data.get('correction'),
|
|
'feedback': data.get('feedback'),
|
|
'executed_manually': decision == 'manual'
|
|
}
|
|
|
|
success = executor.submit_coaching_decision(execution_id, decision_response)
|
|
|
|
if success:
|
|
return jsonify({
|
|
'execution_id': execution_id,
|
|
'decision': decision,
|
|
'status': 'accepted'
|
|
})
|
|
else:
|
|
return jsonify({
|
|
'error': 'Impossible de soumettre la décision'
|
|
}), 400
|
|
|
|
|
|
@executions_bp.route('/<execution_id>/coaching/stats', methods=['GET'])
|
|
def get_coaching_stats(execution_id):
|
|
"""
|
|
Récupère les statistiques COACHING d'une exécution.
|
|
|
|
Returns:
|
|
stats: dict - Statistiques (suggestions, accepted, rejected, etc.)
|
|
"""
|
|
executor = get_executor()
|
|
|
|
if not executor.is_coaching_execution(execution_id):
|
|
return jsonify({
|
|
'error': f'{execution_id} n\'est pas une exécution COACHING'
|
|
}), 400
|
|
|
|
stats = executor.get_coaching_stats(execution_id)
|
|
|
|
if stats is None:
|
|
stats = {
|
|
'suggestions_made': 0,
|
|
'accepted': 0,
|
|
'rejected': 0,
|
|
'corrected': 0,
|
|
'manual_executions': 0
|
|
}
|
|
|
|
return jsonify({
|
|
'execution_id': execution_id,
|
|
'stats': stats
|
|
})
|