697 lines
25 KiB
Python
697 lines
25 KiB
Python
"""
|
|
Gestionnaire de suggestions pour le Mode Assisté.
|
|
Gère les suggestions en temps réel, les scores de confiance et les timeouts.
|
|
"""
|
|
|
|
import time
|
|
from typing import Dict, Any, Optional, Callable, List
|
|
from datetime import datetime, timedelta
|
|
from threading import Lock
|
|
|
|
from .learning_manager import LearningManager
|
|
from .embeddings_manager import EmbeddingsManager
|
|
from .logger import Logger
|
|
from .workflow_matcher import WorkflowMatcher, WorkflowMatch
|
|
|
|
|
|
class SuggestionManager:
|
|
"""
|
|
Gestionnaire de suggestions pour le Mode Assisté.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
learning_manager: LearningManager,
|
|
embeddings_manager: EmbeddingsManager,
|
|
logger: Logger,
|
|
config: Dict[str, Any],
|
|
workflow_matcher: Optional[WorkflowMatcher] = None
|
|
):
|
|
"""
|
|
Initialise le gestionnaire de suggestions.
|
|
|
|
Args:
|
|
learning_manager: Gestionnaire d'apprentissage
|
|
embeddings_manager: Gestionnaire d'embeddings
|
|
logger: Logger
|
|
config: Configuration
|
|
workflow_matcher: Matcher de workflows (optionnel)
|
|
"""
|
|
self.learning_manager = learning_manager
|
|
self.embeddings_manager = embeddings_manager
|
|
self.logger = logger
|
|
self.config = config
|
|
|
|
# WorkflowMatcher pour la détection de workflows
|
|
self.workflow_matcher = workflow_matcher or WorkflowMatcher(logger, config)
|
|
|
|
# Configuration
|
|
self.similarity_threshold = config.get("assist", {}).get(
|
|
"similarity_threshold", 0.75
|
|
)
|
|
self.suggestion_timeout = config.get("assist", {}).get(
|
|
"suggestion_timeout", 10.0 # secondes
|
|
)
|
|
self.workflow_confidence_threshold = config.get("workflow", {}).get(
|
|
"min_confidence", 0.80 # 80% par défaut
|
|
)
|
|
|
|
# État actuel
|
|
self.current_suggestion: Optional[Dict[str, Any]] = None
|
|
self.suggestion_lock = Lock()
|
|
self.suggestion_start_time: Optional[datetime] = None
|
|
|
|
# Tracking des rejets par workflow
|
|
self.workflow_rejections: Dict[str, int] = {} # workflow_id -> count
|
|
self.workflow_priority_adjustments: Dict[str, float] = {} # workflow_id -> multiplier
|
|
|
|
# Callbacks
|
|
self.on_suggestion_created: Optional[Callable] = None
|
|
self.on_suggestion_accepted: Optional[Callable] = None
|
|
self.on_suggestion_rejected: Optional[Callable] = None
|
|
self.on_suggestion_timeout: Optional[Callable] = None
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_manager_initialized",
|
|
"similarity_threshold": self.similarity_threshold,
|
|
"timeout": self.suggestion_timeout,
|
|
"workflow_confidence_threshold": self.workflow_confidence_threshold
|
|
})
|
|
|
|
def find_suggestion(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Recherche une suggestion basée sur le contexte actuel.
|
|
|
|
Args:
|
|
context: Contexte actuel (embedding, fenêtre, etc.)
|
|
|
|
Returns:
|
|
Suggestion ou None
|
|
"""
|
|
# D'abord, vérifier s'il y a un workflow en cours
|
|
workflow_suggestion = self._check_workflow_suggestion(context)
|
|
if workflow_suggestion:
|
|
return workflow_suggestion
|
|
|
|
# Sinon, recherche classique par embedding
|
|
embedding = context.get("embedding")
|
|
|
|
if embedding is None:
|
|
return None
|
|
|
|
# Rechercher dans FAISS
|
|
results = self.embeddings_manager.search_similar(embedding, k=3)
|
|
|
|
if not results:
|
|
return None
|
|
|
|
# Filtrer par seuil de similarité
|
|
best_match = results[0]
|
|
|
|
if best_match["similarity"] < self.similarity_threshold:
|
|
return None
|
|
|
|
# Récupérer les métadonnées
|
|
metadata = best_match["metadata"]
|
|
task_id = metadata.get("task_id")
|
|
|
|
if not task_id:
|
|
return None
|
|
|
|
# Charger la tâche
|
|
task = self.learning_manager.load_task(task_id)
|
|
|
|
if not task:
|
|
return None
|
|
|
|
# Créer la suggestion
|
|
suggestion = {
|
|
"type": "action", # Type de suggestion
|
|
"task_id": task_id,
|
|
"task_name": task.task_name,
|
|
"action_type": metadata.get("action_type", "unknown"),
|
|
"description": metadata.get("description", ""),
|
|
"similarity": best_match["similarity"],
|
|
"confidence": self._calculate_confidence(best_match, task),
|
|
"metadata": metadata,
|
|
"timestamp": datetime.now()
|
|
}
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_found",
|
|
"task_id": task_id,
|
|
"similarity": best_match["similarity"],
|
|
"confidence": suggestion["confidence"]
|
|
})
|
|
|
|
return suggestion
|
|
|
|
def _check_workflow_suggestion(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Vérifie s'il y a un workflow en cours qui correspond au contexte.
|
|
|
|
Args:
|
|
context: Contexte actuel
|
|
|
|
Returns:
|
|
Suggestion de workflow ou None
|
|
"""
|
|
# Récupérer l'event_capture depuis le contexte
|
|
event_capture = context.get("event_capture")
|
|
if not event_capture:
|
|
return None
|
|
|
|
# Récupérer les workflows détectés
|
|
workflows = event_capture.get_workflows()
|
|
if not workflows:
|
|
return None
|
|
|
|
# Récupérer la session courante
|
|
current_session = event_capture.session_manager.current_session
|
|
if not current_session or not current_session.actions:
|
|
return None
|
|
|
|
# Comparer avec les workflows connus
|
|
for workflow in workflows:
|
|
# Vérifier si on est au début d'un workflow
|
|
match_score = self._match_workflow_start(current_session.actions, workflow)
|
|
|
|
if match_score >= 0.8: # 80% de correspondance
|
|
# Calculer quelle est la prochaine étape
|
|
next_step_index = len(current_session.actions)
|
|
|
|
if next_step_index < len(workflow.steps):
|
|
next_step = workflow.steps[next_step_index]
|
|
|
|
# Créer une suggestion de workflow
|
|
suggestion = {
|
|
"type": "workflow", # Type workflow
|
|
"workflow_id": workflow.workflow_id,
|
|
"workflow_name": workflow.name,
|
|
"current_step": next_step_index,
|
|
"total_steps": len(workflow.steps),
|
|
"next_action": {
|
|
"action_type": next_step.action_type,
|
|
"description": next_step.target_description,
|
|
"position": next_step.position
|
|
},
|
|
"remaining_steps": len(workflow.steps) - next_step_index,
|
|
"confidence": workflow.confidence * match_score,
|
|
"repetitions": workflow.repetitions,
|
|
"timestamp": datetime.now()
|
|
}
|
|
|
|
self.logger.log_action({
|
|
"action": "workflow_suggestion_found",
|
|
"workflow_id": workflow.workflow_id,
|
|
"step": next_step_index,
|
|
"confidence": suggestion["confidence"]
|
|
})
|
|
|
|
return suggestion
|
|
|
|
return None
|
|
|
|
def _match_workflow_start(self, current_actions: list, workflow) -> float:
|
|
"""
|
|
Calcule le score de correspondance entre les actions courantes et le début d'un workflow.
|
|
|
|
Args:
|
|
current_actions: Actions de la session courante
|
|
workflow: Workflow à comparer
|
|
|
|
Returns:
|
|
Score de correspondance (0-1)
|
|
"""
|
|
if not current_actions or not workflow.steps:
|
|
return 0.0
|
|
|
|
# Comparer les N premières actions
|
|
n = min(len(current_actions), len(workflow.steps))
|
|
matches = 0
|
|
|
|
for i in range(n):
|
|
action = current_actions[i]
|
|
step = workflow.steps[i]
|
|
|
|
# Comparer le type d'action
|
|
if action.get("action_type") == step.action_type:
|
|
matches += 1
|
|
|
|
# Bonus si même fenêtre
|
|
if action.get("window") == step.window:
|
|
matches += 0.5
|
|
|
|
# Score normalisé
|
|
max_score = n * 1.5 # 1 pour le type + 0.5 pour la fenêtre
|
|
return matches / max_score if max_score > 0 else 0.0
|
|
|
|
def _calculate_confidence(
|
|
self,
|
|
match: Dict[str, Any],
|
|
task: Any
|
|
) -> float:
|
|
"""
|
|
Calcule le score de confiance pour une suggestion.
|
|
|
|
Args:
|
|
match: Résultat de recherche FAISS
|
|
task: Profil de tâche
|
|
|
|
Returns:
|
|
Score de confiance (0-1)
|
|
"""
|
|
# Similarité visuelle
|
|
vision_score = match["similarity"]
|
|
|
|
# Performance historique
|
|
historical_score = task.concordance_rate if hasattr(task, "concordance_rate") else 0.5
|
|
|
|
# Formule : 70% vision + 30% historique
|
|
confidence = 0.7 * vision_score + 0.3 * historical_score
|
|
|
|
return max(0.0, min(1.0, confidence))
|
|
|
|
def create_suggestion(self, context: Dict[str, Any]) -> bool:
|
|
"""
|
|
Crée une nouvelle suggestion si applicable.
|
|
|
|
Args:
|
|
context: Contexte actuel
|
|
|
|
Returns:
|
|
True si suggestion créée
|
|
"""
|
|
with self.suggestion_lock:
|
|
# Vérifier qu'il n'y a pas déjà une suggestion active
|
|
if self.current_suggestion is not None:
|
|
return False
|
|
|
|
# Rechercher une suggestion
|
|
suggestion = self.find_suggestion(context)
|
|
|
|
if suggestion is None:
|
|
return False
|
|
|
|
# Vérifier le seuil de confiance
|
|
if suggestion["confidence"] < self.similarity_threshold:
|
|
return False
|
|
|
|
# Créer la suggestion
|
|
self.current_suggestion = suggestion
|
|
self.suggestion_start_time = datetime.now()
|
|
|
|
# Callback
|
|
if self.on_suggestion_created:
|
|
self.on_suggestion_created(suggestion)
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_created",
|
|
"task_id": suggestion["task_id"],
|
|
"confidence": suggestion["confidence"]
|
|
})
|
|
|
|
return True
|
|
|
|
def accept_suggestion(self) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Accepte la suggestion actuelle.
|
|
|
|
Returns:
|
|
Suggestion acceptée ou None
|
|
"""
|
|
with self.suggestion_lock:
|
|
if self.current_suggestion is None:
|
|
return None
|
|
|
|
suggestion = self.current_suggestion
|
|
self.current_suggestion = None
|
|
self.suggestion_start_time = None
|
|
|
|
# Si c'est une suggestion de workflow, tracker l'acceptation
|
|
if suggestion.get("type") == "workflow":
|
|
workflow_id = suggestion.get("workflow_id")
|
|
if workflow_id:
|
|
self._track_workflow_acceptance(workflow_id)
|
|
|
|
# Mettre à jour les statistiques (pour les suggestions d'action)
|
|
if suggestion.get("task_id"):
|
|
self.learning_manager.confirm_action({
|
|
"type": "accept",
|
|
"task_id": suggestion["task_id"]
|
|
})
|
|
|
|
# Callback
|
|
if self.on_suggestion_accepted:
|
|
self.on_suggestion_accepted(suggestion)
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_accepted",
|
|
"suggestion_type": suggestion.get("type"),
|
|
"workflow_id": suggestion.get("workflow_id"),
|
|
"task_id": suggestion.get("task_id")
|
|
})
|
|
|
|
return suggestion
|
|
|
|
def reject_suggestion(self) -> bool:
|
|
"""
|
|
Rejette la suggestion actuelle.
|
|
|
|
Returns:
|
|
True si suggestion rejetée
|
|
"""
|
|
with self.suggestion_lock:
|
|
if self.current_suggestion is None:
|
|
return False
|
|
|
|
suggestion = self.current_suggestion
|
|
self.current_suggestion = None
|
|
self.suggestion_start_time = None
|
|
|
|
# Si c'est une suggestion de workflow, tracker le rejet
|
|
if suggestion.get("type") == "workflow":
|
|
workflow_id = suggestion.get("workflow_id")
|
|
if workflow_id:
|
|
self._track_workflow_rejection(workflow_id)
|
|
|
|
# Mettre à jour les statistiques (pour les suggestions d'action)
|
|
if suggestion.get("task_id"):
|
|
self.learning_manager.confirm_action({
|
|
"type": "reject",
|
|
"task_id": suggestion["task_id"]
|
|
})
|
|
|
|
# Callback
|
|
if self.on_suggestion_rejected:
|
|
self.on_suggestion_rejected(suggestion)
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_rejected",
|
|
"suggestion_type": suggestion.get("type"),
|
|
"workflow_id": suggestion.get("workflow_id"),
|
|
"task_id": suggestion.get("task_id")
|
|
})
|
|
|
|
return True
|
|
|
|
def check_timeout(self) -> bool:
|
|
"""
|
|
Vérifie si la suggestion actuelle a expiré.
|
|
|
|
Returns:
|
|
True si timeout
|
|
"""
|
|
with self.suggestion_lock:
|
|
if self.current_suggestion is None:
|
|
return False
|
|
|
|
if self.suggestion_start_time is None:
|
|
return False
|
|
|
|
elapsed = (datetime.now() - self.suggestion_start_time).total_seconds()
|
|
|
|
if elapsed >= self.suggestion_timeout:
|
|
# Timeout
|
|
suggestion = self.current_suggestion
|
|
self.current_suggestion = None
|
|
self.suggestion_start_time = None
|
|
|
|
# Callback
|
|
if self.on_suggestion_timeout:
|
|
self.on_suggestion_timeout(suggestion)
|
|
|
|
self.logger.log_action({
|
|
"action": "suggestion_timeout",
|
|
"task_id": suggestion["task_id"],
|
|
"elapsed": elapsed
|
|
})
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_current_suggestion(self) -> Optional[Dict[str, Any]]:
|
|
"""Retourne la suggestion actuelle."""
|
|
with self.suggestion_lock:
|
|
return self.current_suggestion
|
|
|
|
def clear_suggestion(self):
|
|
"""Efface la suggestion actuelle."""
|
|
with self.suggestion_lock:
|
|
self.current_suggestion = None
|
|
self.suggestion_start_time = None
|
|
|
|
def get_stats(self) -> Dict[str, Any]:
|
|
"""Retourne les statistiques du gestionnaire."""
|
|
return {
|
|
"has_active_suggestion": self.current_suggestion is not None,
|
|
"similarity_threshold": self.similarity_threshold,
|
|
"timeout": self.suggestion_timeout,
|
|
"workflow_rejections": len(self.workflow_rejections),
|
|
"workflows_with_adjusted_priority": len(self.workflow_priority_adjustments)
|
|
}
|
|
|
|
def check_workflow_match(
|
|
self,
|
|
session_actions: List[Dict[str, Any]],
|
|
workflows: List[Dict[str, Any]]
|
|
) -> Optional[WorkflowMatch]:
|
|
"""
|
|
Vérifie périodiquement si les actions courantes correspondent à un workflow connu.
|
|
|
|
Cette méthode doit être appelée régulièrement (ex: toutes les 2s) en mode Assist
|
|
pour détecter les correspondances de workflows.
|
|
|
|
Args:
|
|
session_actions: Liste des actions de la session courante
|
|
workflows: Liste des workflows connus
|
|
|
|
Returns:
|
|
Meilleure correspondance si trouvée, None sinon
|
|
"""
|
|
if not session_actions or not workflows:
|
|
return None
|
|
|
|
# Vérifier qu'il n'y a pas déjà une suggestion active
|
|
with self.suggestion_lock:
|
|
if self.current_suggestion is not None:
|
|
return None
|
|
|
|
# Utiliser le WorkflowMatcher pour trouver les correspondances
|
|
matches = self.workflow_matcher.match_current_session(
|
|
session_actions,
|
|
workflows
|
|
)
|
|
|
|
if not matches:
|
|
return None
|
|
|
|
# Appliquer les ajustements de priorité basés sur les rejets
|
|
adjusted_matches = []
|
|
for match in matches:
|
|
adjusted_confidence = self._apply_priority_adjustment(match)
|
|
|
|
# Créer un nouveau match avec la confiance ajustée
|
|
adjusted_match = WorkflowMatch(
|
|
workflow_id=match.workflow_id,
|
|
workflow_name=match.workflow_name,
|
|
confidence=adjusted_confidence,
|
|
matched_steps=match.matched_steps,
|
|
total_steps=match.total_steps,
|
|
remaining_steps=match.remaining_steps,
|
|
current_step_index=match.current_step_index
|
|
)
|
|
adjusted_matches.append(adjusted_match)
|
|
|
|
# Trier à nouveau par confiance ajustée
|
|
adjusted_matches.sort(key=lambda m: m.confidence, reverse=True)
|
|
|
|
# Trouver le meilleur match
|
|
best_match = self.workflow_matcher.find_best_match(adjusted_matches)
|
|
|
|
if best_match:
|
|
self.logger.log_action({
|
|
"action": "workflow_match_found",
|
|
"workflow_id": best_match.workflow_id,
|
|
"workflow_name": best_match.workflow_name,
|
|
"confidence": best_match.confidence,
|
|
"matched_steps": best_match.matched_steps,
|
|
"remaining_steps": len(best_match.remaining_steps)
|
|
})
|
|
|
|
return best_match
|
|
|
|
def create_workflow_suggestion(
|
|
self,
|
|
workflow_match: WorkflowMatch
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""
|
|
Crée une suggestion de workflow avec les détails des étapes restantes.
|
|
|
|
Args:
|
|
workflow_match: Correspondance de workflow trouvée
|
|
|
|
Returns:
|
|
Suggestion créée ou None si impossible
|
|
"""
|
|
with self.suggestion_lock:
|
|
# Vérifier qu'il n'y a pas déjà une suggestion active
|
|
if self.current_suggestion is not None:
|
|
return None
|
|
|
|
# Vérifier le seuil de confiance
|
|
if workflow_match.confidence < self.workflow_confidence_threshold:
|
|
self.logger.log_action({
|
|
"action": "workflow_suggestion_rejected_low_confidence",
|
|
"workflow_id": workflow_match.workflow_id,
|
|
"confidence": workflow_match.confidence,
|
|
"threshold": self.workflow_confidence_threshold
|
|
})
|
|
return None
|
|
|
|
# Créer la suggestion avec les détails des étapes
|
|
suggestion = {
|
|
"type": "workflow",
|
|
"workflow_id": workflow_match.workflow_id,
|
|
"workflow_name": workflow_match.workflow_name,
|
|
"confidence": workflow_match.confidence,
|
|
"current_step": workflow_match.current_step_index,
|
|
"total_steps": workflow_match.total_steps,
|
|
"matched_steps": workflow_match.matched_steps,
|
|
"remaining_steps": workflow_match.remaining_steps,
|
|
"next_steps_preview": workflow_match.remaining_steps[:3], # 3 prochaines étapes
|
|
"created_at": datetime.now(),
|
|
"timeout": self.suggestion_timeout
|
|
}
|
|
|
|
# Enregistrer la suggestion
|
|
self.current_suggestion = suggestion
|
|
self.suggestion_start_time = datetime.now()
|
|
|
|
# Callback
|
|
if self.on_suggestion_created:
|
|
self.on_suggestion_created(suggestion)
|
|
|
|
self.logger.log_action({
|
|
"action": "workflow_suggestion_created",
|
|
"workflow_id": workflow_match.workflow_id,
|
|
"workflow_name": workflow_match.workflow_name,
|
|
"confidence": workflow_match.confidence,
|
|
"remaining_steps": len(workflow_match.remaining_steps)
|
|
})
|
|
|
|
return suggestion
|
|
|
|
def _apply_priority_adjustment(self, match: WorkflowMatch) -> float:
|
|
"""
|
|
Applique l'ajustement de priorité basé sur les rejets précédents.
|
|
|
|
Args:
|
|
match: Correspondance de workflow
|
|
|
|
Returns:
|
|
Confiance ajustée
|
|
"""
|
|
workflow_id = match.workflow_id
|
|
|
|
# Récupérer le multiplicateur d'ajustement
|
|
adjustment = self.workflow_priority_adjustments.get(workflow_id, 1.0)
|
|
|
|
# Appliquer l'ajustement
|
|
adjusted_confidence = match.confidence * adjustment
|
|
|
|
# S'assurer que la confiance reste dans [0, 1]
|
|
adjusted_confidence = max(0.0, min(1.0, adjusted_confidence))
|
|
|
|
if adjustment != 1.0:
|
|
self.logger.log_action({
|
|
"action": "priority_adjustment_applied",
|
|
"workflow_id": workflow_id,
|
|
"original_confidence": match.confidence,
|
|
"adjusted_confidence": adjusted_confidence,
|
|
"adjustment_multiplier": adjustment
|
|
})
|
|
|
|
return adjusted_confidence
|
|
|
|
def _track_workflow_rejection(self, workflow_id: str):
|
|
"""
|
|
Enregistre un rejet de workflow et ajuste la priorité si nécessaire.
|
|
|
|
Après 3 rejets, la priorité du workflow est réduite (confiance * 0.9).
|
|
|
|
Args:
|
|
workflow_id: ID du workflow rejeté
|
|
"""
|
|
# Incrémenter le compteur de rejets
|
|
current_rejections = self.workflow_rejections.get(workflow_id, 0)
|
|
current_rejections += 1
|
|
self.workflow_rejections[workflow_id] = current_rejections
|
|
|
|
self.logger.log_action({
|
|
"action": "workflow_rejection_tracked",
|
|
"workflow_id": workflow_id,
|
|
"total_rejections": current_rejections
|
|
})
|
|
|
|
# Après 3 rejets, ajuster la priorité
|
|
if current_rejections >= 3:
|
|
# Réduire la confiance de 10% à chaque tranche de 3 rejets
|
|
adjustment_factor = 0.9 ** (current_rejections // 3)
|
|
self.workflow_priority_adjustments[workflow_id] = adjustment_factor
|
|
|
|
self.logger.log_action({
|
|
"action": "workflow_priority_adjusted",
|
|
"workflow_id": workflow_id,
|
|
"rejections": current_rejections,
|
|
"new_adjustment_factor": adjustment_factor
|
|
})
|
|
|
|
def _track_workflow_acceptance(self, workflow_id: str):
|
|
"""
|
|
Enregistre une acceptation de workflow et améliore la priorité.
|
|
|
|
Args:
|
|
workflow_id: ID du workflow accepté
|
|
"""
|
|
# Réduire le compteur de rejets (récompenser les acceptations)
|
|
current_rejections = self.workflow_rejections.get(workflow_id, 0)
|
|
if current_rejections > 0:
|
|
current_rejections = max(0, current_rejections - 2) # Réduire de 2
|
|
self.workflow_rejections[workflow_id] = current_rejections
|
|
|
|
# Recalculer l'ajustement de priorité
|
|
if current_rejections >= 3:
|
|
adjustment_factor = 0.9 ** (current_rejections // 3)
|
|
self.workflow_priority_adjustments[workflow_id] = adjustment_factor
|
|
else:
|
|
# Retirer l'ajustement si moins de 3 rejets
|
|
if workflow_id in self.workflow_priority_adjustments:
|
|
del self.workflow_priority_adjustments[workflow_id]
|
|
|
|
self.logger.log_action({
|
|
"action": "workflow_acceptance_tracked",
|
|
"workflow_id": workflow_id,
|
|
"remaining_rejections": current_rejections
|
|
})
|
|
|
|
def on_workflow_detected(self, workflow: Dict[str, Any]):
|
|
"""
|
|
Callback appelé quand un workflow est détecté.
|
|
Peut créer une suggestion immédiate si le workflow est pertinent.
|
|
|
|
Args:
|
|
workflow: Workflow détecté
|
|
"""
|
|
self.logger.log_action({
|
|
"action": "workflow_detected_in_suggestion_manager",
|
|
"workflow_id": workflow.get("workflow_id"),
|
|
"workflow_name": workflow.get("name"),
|
|
"confidence": workflow.get("confidence")
|
|
})
|
|
|
|
# Pour l'instant, on log seulement
|
|
# Dans le futur, on pourrait créer une suggestion proactive
|
|
# basée sur le workflow détecté
|