Files
rpa_vision_v3/scripts/diagnostic_proprietes_etapes_vides_12jan2026.py
Dom a27b74cf22 v1.0 - Version stable: multi-PC, détection UI-DETR-1, 3 modes exécution
- Frontend v4 accessible sur réseau local (192.168.1.40)
- Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard)
- Ollama GPU fonctionnel
- Self-healing interactif
- Dashboard confiance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 11:23:51 +01:00

647 lines
27 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Script de Diagnostic - Propriétés d'Étapes Vides VWB
Auteur : Dom, Alice, Kiro - 12 janvier 2026
Ce script diagnostique le problème des propriétés d'étapes vides dans le Visual Workflow Builder
en analysant la configuration, les types d'étapes et les mappings.
"""
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Any, Optional
import subprocess
import re
class VWBPropertiesDiagnostic:
"""Diagnostic complet du système de propriétés d'étapes VWB."""
def __init__(self):
"""Initialise le diagnostic."""
self.project_root = Path(__file__).parent.parent
self.frontend_path = self.project_root / "visual_workflow_builder" / "frontend"
self.backend_path = self.project_root / "visual_workflow_builder" / "backend"
self.results = {
"timestamp": "2026-01-12",
"diagnostic_version": "1.0.0",
"issues_found": [],
"recommendations": [],
"file_analysis": {},
"type_mappings": {},
"vwb_actions": {},
"configuration_status": {}
}
print("🔍 Diagnostic des Propriétés d'Étapes VWB - Démarrage")
print(f"📁 Racine du projet : {self.project_root}")
def run_full_diagnostic(self) -> Dict[str, Any]:
"""Exécute le diagnostic complet."""
try:
print("\n" + "="*60)
print("🔍 DIAGNOSTIC COMPLET DES PROPRIÉTÉS D'ÉTAPES")
print("="*60)
# 1. Analyser la configuration des paramètres
self._analyze_step_parameters_config()
# 2. Analyser les types d'étapes
self._analyze_step_types()
# 3. Analyser les actions VWB
self._analyze_vwb_actions()
# 4. Analyser les hooks d'intégration
self._analyze_integration_hooks()
# 5. Analyser le composant PropertiesPanel
self._analyze_properties_panel()
# 6. Vérifier la cohérence TypeScript
self._check_typescript_consistency()
# 7. Générer les recommandations
self._generate_recommendations()
# 8. Sauvegarder le rapport
self._save_diagnostic_report()
print(f"\n✅ Diagnostic terminé - {len(self.results['issues_found'])} problèmes identifiés")
return self.results
except Exception as e:
print(f"❌ Erreur lors du diagnostic : {e}")
self.results["fatal_error"] = str(e)
return self.results
def _analyze_step_parameters_config(self):
"""Analyse la configuration stepParametersConfig."""
print("\n📋 Analyse de la configuration stepParametersConfig...")
properties_panel_path = self.frontend_path / "src" / "components" / "PropertiesPanel" / "index.tsx"
if not properties_panel_path.exists():
self._add_issue("CRITICAL", "Fichier PropertiesPanel/index.tsx introuvable", {
"expected_path": str(properties_panel_path)
})
return
try:
content = properties_panel_path.read_text(encoding='utf-8')
# Extraire la configuration stepParametersConfig
config_match = re.search(
r'const stepParametersConfig:\s*Record<StepType,\s*ParameterConfig\[\]>\s*=\s*{([^}]+)}',
content,
re.DOTALL
)
if config_match:
config_content = config_match.group(1)
# Extraire les types configurés
type_matches = re.findall(r'(\w+):\s*\[', config_content)
configured_types = set(type_matches)
self.results["configuration_status"]["stepParametersConfig"] = {
"found": True,
"configured_types": list(configured_types),
"type_count": len(configured_types)
}
print(f" ✅ Configuration trouvée avec {len(configured_types)} types :")
for step_type in sorted(configured_types):
print(f" - {step_type}")
# Analyser chaque type configuré
for step_type in configured_types:
self._analyze_step_type_config(content, step_type)
else:
self._add_issue("CRITICAL", "Configuration stepParametersConfig non trouvée", {
"file": str(properties_panel_path),
"search_pattern": "stepParametersConfig"
})
except Exception as e:
self._add_issue("ERROR", f"Erreur lecture PropertiesPanel : {e}", {
"file": str(properties_panel_path)
})
def _analyze_step_type_config(self, content: str, step_type: str):
"""Analyse la configuration d'un type d'étape spécifique."""
# Extraire la configuration pour ce type
pattern = rf'{step_type}:\s*\[(.*?)\]'
match = re.search(pattern, content, re.DOTALL)
if match:
config_content = match.group(1)
# Compter les paramètres
param_matches = re.findall(r'{\s*name:\s*[\'"](\w+)[\'"]', config_content)
self.results["type_mappings"][step_type] = {
"parameters": param_matches,
"parameter_count": len(param_matches),
"has_required_params": "required: true" in config_content,
"has_visual_params": "type: 'visual'" in config_content
}
print(f" 📝 {step_type}: {len(param_matches)} paramètres")
def _analyze_step_types(self):
"""Analyse les types d'étapes définis."""
print("\n🏷️ Analyse des types d'étapes...")
types_path = self.frontend_path / "src" / "types" / "index.ts"
if not types_path.exists():
self._add_issue("CRITICAL", "Fichier types/index.ts introuvable", {
"expected_path": str(types_path)
})
return
try:
content = types_path.read_text(encoding='utf-8')
# Chercher la définition de StepType
step_type_match = re.search(
r'export\s+type\s+StepType\s*=\s*([^;]+);',
content,
re.DOTALL
)
if step_type_match:
step_type_def = step_type_match.group(1)
# Extraire les types définis
type_matches = re.findall(r"['\"](\w+)['\"]", step_type_def)
defined_types = set(type_matches)
self.results["configuration_status"]["stepTypes"] = {
"found": True,
"defined_types": list(defined_types),
"type_count": len(defined_types)
}
print(f" ✅ Types StepType trouvés ({len(defined_types)}) :")
for step_type in sorted(defined_types):
print(f" - {step_type}")
# Comparer avec la configuration
configured_types = set(self.results["configuration_status"].get("stepParametersConfig", {}).get("configured_types", []))
missing_in_config = defined_types - configured_types
extra_in_config = configured_types - defined_types
if missing_in_config:
self._add_issue("WARNING", "Types définis mais non configurés", {
"missing_types": list(missing_in_config)
})
if extra_in_config:
self._add_issue("WARNING", "Types configurés mais non définis", {
"extra_types": list(extra_in_config)
})
else:
self._add_issue("CRITICAL", "Définition StepType non trouvée", {
"file": str(types_path)
})
except Exception as e:
self._add_issue("ERROR", f"Erreur lecture types : {e}", {
"file": str(types_path)
})
def _analyze_vwb_actions(self):
"""Analyse les actions VWB du catalogue."""
print("\n🎯 Analyse des actions VWB...")
# Analyser le catalogue statique
static_catalog_path = self.frontend_path / "src" / "data" / "staticCatalog.ts"
if static_catalog_path.exists():
try:
content = static_catalog_path.read_text(encoding='utf-8')
# Extraire les IDs d'actions
id_matches = re.findall(r"id:\s*['\"](\w+)['\"]", content)
vwb_action_ids = set(id_matches)
self.results["vwb_actions"]["static_catalog"] = {
"found": True,
"action_ids": list(vwb_action_ids),
"action_count": len(vwb_action_ids)
}
print(f" ✅ Catalogue statique trouvé avec {len(vwb_action_ids)} actions :")
for action_id in sorted(vwb_action_ids):
print(f" - {action_id}")
except Exception as e:
self._add_issue("ERROR", f"Erreur lecture catalogue statique : {e}", {
"file": str(static_catalog_path)
})
# Analyser le registry backend
registry_path = self.backend_path / "actions" / "registry.py"
if registry_path.exists():
try:
content = registry_path.read_text(encoding='utf-8')
# Vérifier la présence du registry
if "class VWBActionRegistry" in content:
self.results["vwb_actions"]["backend_registry"] = {
"found": True,
"has_auto_discovery": "auto_discover_actions" in content,
"has_global_instance": "get_global_registry" in content
}
print(" ✅ Registry backend trouvé et fonctionnel")
else:
self._add_issue("WARNING", "Registry backend incomplet", {
"file": str(registry_path)
})
except Exception as e:
self._add_issue("ERROR", f"Erreur lecture registry : {e}", {
"file": str(registry_path)
})
def _analyze_integration_hooks(self):
"""Analyse les hooks d'intégration VWB."""
print("\n🔗 Analyse des hooks d'intégration...")
hooks_path = self.frontend_path / "src" / "hooks" / "useVWBStepIntegration.ts"
if not hooks_path.exists():
self._add_issue("CRITICAL", "Hooks d'intégration VWB introuvables", {
"expected_path": str(hooks_path)
})
return
try:
content = hooks_path.read_text(encoding='utf-8')
# Vérifier les hooks principaux
hooks_found = {
"useVWBStepIntegration": "useVWBStepIntegration" in content,
"useIsVWBStep": "useIsVWBStep" in content,
"useVWBActionId": "useVWBActionId" in content
}
self.results["configuration_status"]["integration_hooks"] = hooks_found
print(" 🔍 Hooks d'intégration :")
for hook_name, found in hooks_found.items():
status = "" if found else ""
print(f" {status} {hook_name}")
if not found:
self._add_issue("CRITICAL", f"Hook {hook_name} manquant", {
"file": str(hooks_path)
})
# Analyser la logique de détection VWB
if "useIsVWBStep" in content:
# Chercher la logique de détection
detection_patterns = [
"isVWBCatalogAction",
"vwbActionId",
"startsWith('vwb_')",
"includes('catalog_')"
]
detection_logic = {}
for pattern in detection_patterns:
detection_logic[pattern] = pattern in content
self.results["configuration_status"]["vwb_detection_logic"] = detection_logic
print(" 🎯 Logique de détection VWB :")
for pattern, found in detection_logic.items():
status = "" if found else ""
print(f" {status} {pattern}")
except Exception as e:
self._add_issue("ERROR", f"Erreur lecture hooks : {e}", {
"file": str(hooks_path)
})
def _analyze_properties_panel(self):
"""Analyse le composant PropertiesPanel en détail."""
print("\n🎛️ Analyse détaillée du PropertiesPanel...")
properties_panel_path = self.frontend_path / "src" / "components" / "PropertiesPanel" / "index.tsx"
try:
content = properties_panel_path.read_text(encoding='utf-8')
# Analyser la fonction getParameterConfig
get_param_config_match = re.search(
r'const getParameterConfig = useCallback\(\(\):\s*ParameterConfig\[\]\s*=>\s*{([^}]+)}\s*,',
content,
re.DOTALL
)
if get_param_config_match:
function_body = get_param_config_match.group(1)
analysis = {
"function_found": True,
"has_null_check": "if (!selectedStep)" in function_body,
"uses_step_type": "selectedStep.type" in function_body,
"uses_config_lookup": "stepParametersConfig[" in function_body,
"has_fallback": "|| []" in function_body,
"function_body": function_body.strip()
}
self.results["configuration_status"]["getParameterConfig"] = analysis
print(" 🔍 Fonction getParameterConfig :")
for key, value in analysis.items():
if key != "function_body":
status = "" if value else ""
print(f" {status} {key}")
# Identifier le problème potentiel
if analysis["uses_step_type"] and analysis["uses_config_lookup"]:
print(" ⚠️ Problème potentiel identifié :")
print(" La fonction utilise selectedStep.type pour chercher dans stepParametersConfig")
print(" Mais il pourrait y avoir une incohérence entre les types d'étapes créées")
print(" et les clés de configuration disponibles.")
self._add_issue("CRITICAL", "Incohérence potentielle dans getParameterConfig", {
"description": "La fonction getParameterConfig utilise selectedStep.type mais pourrait ne pas trouver la configuration correspondante",
"function_body": function_body.strip()
})
else:
self._add_issue("CRITICAL", "Fonction getParameterConfig non trouvée", {
"file": str(properties_panel_path)
})
# Analyser la logique de rendu conditionnel
vwb_rendering_patterns = [
"isVWBCatalogAction",
"VWBActionProperties",
"vwbAction",
"parameterConfigs.length === 0"
]
rendering_analysis = {}
for pattern in vwb_rendering_patterns:
rendering_analysis[pattern] = pattern in content
self.results["configuration_status"]["rendering_logic"] = rendering_analysis
print(" 🎨 Logique de rendu :")
for pattern, found in rendering_analysis.items():
status = "" if found else ""
print(f" {status} {pattern}")
except Exception as e:
self._add_issue("ERROR", f"Erreur analyse PropertiesPanel : {e}", {
"file": str(properties_panel_path)
})
def _check_typescript_consistency(self):
"""Vérifie la cohérence TypeScript."""
print("\n🔧 Vérification de la cohérence TypeScript...")
try:
# Changer vers le répertoire frontend
frontend_dir = self.frontend_path
if not frontend_dir.exists():
self._add_issue("CRITICAL", "Répertoire frontend introuvable", {
"expected_path": str(frontend_dir)
})
return
# Exécuter la vérification TypeScript
result = subprocess.run(
["npx", "tsc", "--noEmit"],
cwd=frontend_dir,
capture_output=True,
text=True,
timeout=60
)
typescript_status = {
"exit_code": result.returncode,
"has_errors": result.returncode != 0,
"stdout": result.stdout,
"stderr": result.stderr
}
self.results["configuration_status"]["typescript"] = typescript_status
if result.returncode == 0:
print(" ✅ Compilation TypeScript réussie")
else:
print(" ❌ Erreurs TypeScript détectées :")
if result.stderr:
for line in result.stderr.split('\n')[:10]: # Limiter à 10 lignes
if line.strip():
print(f" {line}")
self._add_issue("ERROR", "Erreurs de compilation TypeScript", {
"stderr": result.stderr,
"stdout": result.stdout
})
except subprocess.TimeoutExpired:
self._add_issue("ERROR", "Timeout lors de la vérification TypeScript", {})
except FileNotFoundError:
self._add_issue("WARNING", "npx/tsc non disponible pour la vérification", {})
except Exception as e:
self._add_issue("ERROR", f"Erreur vérification TypeScript : {e}", {})
def _generate_recommendations(self):
"""Génère les recommandations basées sur l'analyse."""
print("\n💡 Génération des recommandations...")
# Analyser les problèmes trouvés
critical_issues = [issue for issue in self.results["issues_found"] if issue["severity"] == "CRITICAL"]
if critical_issues:
self.results["recommendations"].append({
"priority": "URGENT",
"title": "Corriger les problèmes critiques identifiés",
"description": "Plusieurs problèmes critiques empêchent le bon fonctionnement des propriétés d'étapes",
"actions": [
"Vérifier la cohérence entre les types d'étapes et la configuration stepParametersConfig",
"Corriger la logique de détection des actions VWB",
"Ajouter des logs de débogage dans getParameterConfig()",
"Implémenter un système de fallback robuste"
]
})
# Recommandations spécifiques basées sur l'analyse
config_status = self.results["configuration_status"]
if config_status.get("stepParametersConfig", {}).get("found"):
configured_types = set(config_status["stepParametersConfig"]["configured_types"])
defined_types = set(config_status.get("stepTypes", {}).get("defined_types", []))
if configured_types != defined_types:
self.results["recommendations"].append({
"priority": "HIGH",
"title": "Synchroniser les types d'étapes et leur configuration",
"description": "Incohérence entre les types StepType définis et la configuration stepParametersConfig",
"actions": [
f"Ajouter la configuration pour : {list(defined_types - configured_types)}",
f"Supprimer ou corriger : {list(configured_types - defined_types)}",
"Créer un système de validation automatique"
]
})
if not config_status.get("integration_hooks", {}).get("useIsVWBStep", False):
self.results["recommendations"].append({
"priority": "HIGH",
"title": "Corriger les hooks d'intégration VWB",
"description": "Les hooks de détection des actions VWB sont manquants ou défaillants",
"actions": [
"Implémenter useIsVWBStep avec logique robuste",
"Corriger useVWBActionId pour la détection d'ID",
"Ajouter la gestion d'erreurs dans les hooks"
]
})
# Recommandation pour le système de diagnostic
self.results["recommendations"].append({
"priority": "MEDIUM",
"title": "Implémenter un système de diagnostic intégré",
"description": "Ajouter des outils de diagnostic dans l'interface pour faciliter le débogage",
"actions": [
"Créer un composant DebugPanel pour le mode développement",
"Ajouter des logs structurés dans les composants critiques",
"Implémenter des métriques de performance",
"Créer des tests automatisés pour la détection de régression"
]
})
print(f"{len(self.results['recommendations'])} recommandations générées")
def _add_issue(self, severity: str, description: str, details: Dict[str, Any]):
"""Ajoute un problème identifié."""
issue = {
"severity": severity,
"description": description,
"details": details,
"timestamp": "2026-01-12"
}
self.results["issues_found"].append(issue)
# Afficher immédiatement les problèmes critiques
if severity == "CRITICAL":
print(f" 🚨 CRITIQUE : {description}")
def _save_diagnostic_report(self):
"""Sauvegarde le rapport de diagnostic."""
report_path = self.project_root / "docs" / "DIAGNOSTIC_PROPRIETES_ETAPES_VIDES_12JAN2026.json"
try:
# Créer le répertoire docs s'il n'existe pas
report_path.parent.mkdir(exist_ok=True)
# Sauvegarder le rapport JSON
with open(report_path, 'w', encoding='utf-8') as f:
json.dump(self.results, f, indent=2, ensure_ascii=False)
print(f"\n📄 Rapport sauvegardé : {report_path}")
# Créer aussi un résumé markdown
self._create_markdown_summary(report_path.with_suffix('.md'))
except Exception as e:
print(f"❌ Erreur sauvegarde rapport : {e}")
def _create_markdown_summary(self, md_path: Path):
"""Crée un résumé markdown du diagnostic."""
try:
with open(md_path, 'w', encoding='utf-8') as f:
f.write("# Diagnostic des Propriétés d'Étapes Vides - Rapport\n\n")
f.write("**Auteur :** Dom, Alice, Kiro \n")
f.write("**Date :** 12 janvier 2026 \n")
f.write("**Version :** 1.0.0\n\n")
# Résumé exécutif
f.write("## Résumé Exécutif\n\n")
f.write(f"- **Problèmes identifiés :** {len(self.results['issues_found'])}\n")
f.write(f"- **Recommandations :** {len(self.results['recommendations'])}\n")
critical_count = len([i for i in self.results['issues_found'] if i['severity'] == 'CRITICAL'])
f.write(f"- **Problèmes critiques :** {critical_count}\n\n")
# Problèmes identifiés
if self.results['issues_found']:
f.write("## Problèmes Identifiés\n\n")
for issue in self.results['issues_found']:
f.write(f"### {issue['severity']} : {issue['description']}\n\n")
if issue['details']:
f.write("**Détails :**\n")
for key, value in issue['details'].items():
f.write(f"- **{key}** : {value}\n")
f.write("\n")
# Recommandations
if self.results['recommendations']:
f.write("## Recommandations\n\n")
for rec in self.results['recommendations']:
f.write(f"### {rec['priority']} : {rec['title']}\n\n")
f.write(f"{rec['description']}\n\n")
f.write("**Actions recommandées :**\n")
for action in rec['actions']:
f.write(f"- {action}\n")
f.write("\n")
# Configuration actuelle
f.write("## État de la Configuration\n\n")
for section, status in self.results['configuration_status'].items():
f.write(f"### {section}\n\n")
f.write(f"```json\n{json.dumps(status, indent=2)}\n```\n\n")
print(f"📄 Résumé markdown créé : {md_path}")
except Exception as e:
print(f"❌ Erreur création résumé markdown : {e}")
def main():
"""Fonction principale."""
print("🚀 Lancement du diagnostic des propriétés d'étapes VWB")
diagnostic = VWBPropertiesDiagnostic()
results = diagnostic.run_full_diagnostic()
# Afficher le résumé final
print("\n" + "="*60)
print("📊 RÉSUMÉ DU DIAGNOSTIC")
print("="*60)
print(f"✅ Diagnostic terminé avec succès")
print(f"🔍 Problèmes identifiés : {len(results['issues_found'])}")
print(f"💡 Recommandations : {len(results['recommendations'])}")
critical_issues = [i for i in results['issues_found'] if i['severity'] == 'CRITICAL']
if critical_issues:
print(f"🚨 Problèmes critiques : {len(critical_issues)}")
print("\nProblèmes critiques à traiter en priorité :")
for issue in critical_issues:
print(f" - {issue['description']}")
print(f"\n📄 Rapport complet disponible dans docs/")
print("🔧 Utilisez ce diagnostic pour implémenter les corrections nécessaires.")
return 0 if len(critical_issues) == 0 else 1
if __name__ == "__main__":
sys.exit(main())