""" 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('/', 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('//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('//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('//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 })