Files
Geniusia_v2/geniusia2/gui/dialogs/summary_dashboard.py
2026-03-05 00:20:25 +01:00

714 lines
25 KiB
Python

"""
Tableau de bord résumé pour RPA Vision V2
Affiche les statistiques de tâches, niveaux de confiance et historique d'exécution
"""
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
QPushButton, QLabel, QLineEdit, QComboBox, QHeaderView, QFileDialog,
QMessageBox, QWidget, QGroupBox
)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
from PyQt5.QtGui import QFont, QColor, QBrush
from typing import Dict, Any, List, Optional
import logging
import json
import csv
from datetime import datetime
class SummaryDashboard(QDialog):
"""
Tableau de bord affichant les métriques de tâches en temps réel
avec filtrage, recherche et export
"""
# Signal émis quand une tâche est sélectionnée
task_selected = pyqtSignal(str) # task_id
def __init__(self, learning_manager=None, parent=None):
"""
Initialiser le tableau de bord
Args:
learning_manager: Instance du gestionnaire d'apprentissage
parent: Widget parent (optionnel)
"""
super().__init__(parent)
self.learning_manager = learning_manager
self.logger = logging.getLogger(__name__)
# Données des tâches
self.tasks_data: Dict[str, Dict[str, Any]] = {}
# Timer pour mise à jour automatique
self.update_timer = QTimer()
self.update_timer.timeout.connect(self.refresh_data)
self.init_ui()
# Charger les données initiales
if self.learning_manager:
self.refresh_data()
# Démarrer les mises à jour automatiques (toutes les 2 secondes)
self.update_timer.start(2000)
self.logger.info("SummaryDashboard initialisé")
def init_ui(self):
"""Initialiser l'interface utilisateur"""
self.setWindowTitle("Tableau de Bord - RPA Vision V2")
self.setMinimumWidth(1000)
self.setMinimumHeight(600)
# Layout principal
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# Titre
title_label = QLabel("📊 Tableau de Bord des Tâches")
title_label.setFont(QFont("Arial", 16, QFont.Bold))
title_label.setStyleSheet("color: #2196F3; padding: 10px;")
main_layout.addWidget(title_label)
# Barre d'outils (recherche et filtres)
toolbar_layout = QHBoxLayout()
# Recherche
search_label = QLabel("🔍 Recherche:")
search_label.setFont(QFont("Arial", 10))
toolbar_layout.addWidget(search_label)
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Rechercher par nom de tâche...")
self.search_input.setFont(QFont("Arial", 10))
self.search_input.textChanged.connect(self.apply_filters)
toolbar_layout.addWidget(self.search_input)
# Filtre par mode
mode_label = QLabel("Mode:")
mode_label.setFont(QFont("Arial", 10))
toolbar_layout.addWidget(mode_label)
self.mode_filter = QComboBox()
self.mode_filter.setFont(QFont("Arial", 10))
self.mode_filter.addItems(["Tous", "Shadow", "Assisté", "Autopilot"])
self.mode_filter.currentTextChanged.connect(self.apply_filters)
toolbar_layout.addWidget(self.mode_filter)
# Bouton rafraîchir
refresh_button = QPushButton("🔄 Rafraîchir")
refresh_button.setFont(QFont("Arial", 10))
refresh_button.clicked.connect(self.refresh_data)
toolbar_layout.addWidget(refresh_button)
main_layout.addLayout(toolbar_layout)
# Statistiques globales
stats_group = QGroupBox("Statistiques Globales")
stats_group.setFont(QFont("Arial", 11, QFont.Bold))
stats_layout = QHBoxLayout()
stats_group.setLayout(stats_layout)
self.total_tasks_label = QLabel("Total: 0")
self.total_tasks_label.setFont(QFont("Arial", 10))
stats_layout.addWidget(self.total_tasks_label)
self.shadow_tasks_label = QLabel("👀 Shadow: 0")
self.shadow_tasks_label.setFont(QFont("Arial", 10))
stats_layout.addWidget(self.shadow_tasks_label)
self.assist_tasks_label = QLabel("🤝 Assisté: 0")
self.assist_tasks_label.setFont(QFont("Arial", 10))
stats_layout.addWidget(self.assist_tasks_label)
self.auto_tasks_label = QLabel("🤖 Autopilot: 0")
self.auto_tasks_label.setFont(QFont("Arial", 10))
stats_layout.addWidget(self.auto_tasks_label)
stats_layout.addStretch()
main_layout.addWidget(stats_group)
# Tableau des tâches
self.tasks_table = QTableWidget()
self.tasks_table.setFont(QFont("Arial", 9))
self.tasks_table.setColumnCount(8)
self.tasks_table.setHorizontalHeaderLabels([
"Tâche",
"Mode",
"Confiance",
"Observations",
"Concordance",
"Corrections",
"Taux Correction",
"Dernière Exécution"
])
# Configuration du tableau
header = self.tasks_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.Stretch) # Tâche
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Mode
header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # Confiance
header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Observations
header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # Concordance
header.setSectionResizeMode(5, QHeaderView.ResizeToContents) # Corrections
header.setSectionResizeMode(6, QHeaderView.ResizeToContents) # Taux Correction
header.setSectionResizeMode(7, QHeaderView.Stretch) # Dernière Exécution
self.tasks_table.setAlternatingRowColors(True)
self.tasks_table.setSelectionBehavior(QTableWidget.SelectRows)
self.tasks_table.setSelectionMode(QTableWidget.SingleSelection)
self.tasks_table.itemDoubleClicked.connect(self.on_task_double_clicked)
self.tasks_table.setStyleSheet("""
QTableWidget {
border: 2px solid #E0E0E0;
border-radius: 5px;
gridline-color: #E0E0E0;
}
QTableWidget::item {
padding: 5px;
}
QTableWidget::item:selected {
background-color: #E3F2FD;
color: #1976D2;
}
QHeaderView::section {
background-color: #2196F3;
color: white;
padding: 8px;
border: none;
font-weight: bold;
}
""")
main_layout.addWidget(self.tasks_table)
# Boutons d'action
button_layout = QHBoxLayout()
export_csv_button = QPushButton("📄 Exporter CSV")
export_csv_button.setFont(QFont("Arial", 10))
export_csv_button.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #45a049;
}
""")
export_csv_button.clicked.connect(self.export_to_csv)
button_layout.addWidget(export_csv_button)
export_json_button = QPushButton("📋 Exporter JSON")
export_json_button.setFont(QFont("Arial", 10))
export_json_button.setStyleSheet("""
QPushButton {
background-color: #2196F3;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #1976D2;
}
""")
export_json_button.clicked.connect(self.export_to_json)
button_layout.addWidget(export_json_button)
button_layout.addStretch()
close_button = QPushButton("✗ Fermer")
close_button.setFont(QFont("Arial", 10))
close_button.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #da190b;
}
""")
close_button.clicked.connect(self.close)
button_layout.addWidget(close_button)
main_layout.addLayout(button_layout)
# Note d'aide
help_label = QLabel(
"💡 Astuce: Double-cliquez sur une tâche pour voir les détails"
)
help_label.setFont(QFont("Arial", 9))
help_label.setStyleSheet("color: #666; padding: 5px;")
main_layout.addWidget(help_label)
def refresh_data(self):
"""Rafraîchir les données depuis le gestionnaire d'apprentissage"""
if not self.learning_manager:
return
try:
# Obtenir toutes les tâches
tasks = self.learning_manager.get_all_tasks()
# Mettre à jour les données
self.tasks_data = {task["task_id"]: task for task in tasks}
# Mettre à jour les statistiques globales
self.update_global_stats()
# Mettre à jour le tableau
self.update_table()
self.logger.debug(f"Données rafraîchies: {len(self.tasks_data)} tâches")
except Exception as e:
self.logger.error(f"Erreur lors du rafraîchissement: {e}")
def update_global_stats(self):
"""Mettre à jour les statistiques globales"""
if not self.learning_manager:
return
try:
stats = self.learning_manager.get_task_stats()
self.total_tasks_label.setText(f"Total: {stats.get('total_tasks', 0)}")
self.shadow_tasks_label.setText(f"👀 Shadow: {stats.get('shadow_tasks', 0)}")
self.assist_tasks_label.setText(f"🤝 Assisté: {stats.get('assist_tasks', 0)}")
self.auto_tasks_label.setText(f"🤖 Autopilot: {stats.get('auto_tasks', 0)}")
except Exception as e:
self.logger.error(f"Erreur lors de la mise à jour des stats: {e}")
def update_metrics(self, task_id: str, metrics: Dict[str, Any]):
"""
Mettre à jour les métriques d'une tâche spécifique
Args:
task_id: ID de la tâche
metrics: Dictionnaire de métriques
"""
if task_id in self.tasks_data:
self.tasks_data[task_id].update(metrics)
else:
self.tasks_data[task_id] = metrics
# Mettre à jour le tableau
self.update_table()
self.logger.debug(f"Métriques mises à jour pour tâche: {task_id}")
def update_table(self):
"""Mettre à jour le tableau avec les données filtrées"""
# Appliquer les filtres
filtered_tasks = self.get_filtered_tasks()
# Mettre à jour le nombre de lignes
self.tasks_table.setRowCount(len(filtered_tasks))
# Remplir le tableau
for row, task in enumerate(filtered_tasks):
self._populate_row(row, task)
self.logger.debug(f"Tableau mis à jour: {len(filtered_tasks)} tâches affichées")
def _populate_row(self, row: int, task: Dict[str, Any]):
"""
Remplir une ligne du tableau avec les données d'une tâche
Args:
row: Numéro de ligne
task: Dictionnaire de données de tâche
"""
# Tâche
task_item = QTableWidgetItem(task.get("task_name", ""))
task_item.setData(Qt.UserRole, task.get("task_id"))
self.tasks_table.setItem(row, 0, task_item)
# Mode
mode = task.get("mode", "shadow")
mode_icons = {"shadow": "👀", "assist": "🤝", "auto": "🤖"}
mode_names = {"shadow": "Shadow", "assist": "Assisté", "auto": "Autopilot"}
mode_text = f"{mode_icons.get(mode, '')} {mode_names.get(mode, mode)}"
mode_item = QTableWidgetItem(mode_text)
mode_item.setTextAlignment(Qt.AlignCenter)
# Couleur selon le mode
mode_colors = {
"shadow": QColor(33, 150, 243), # Bleu
"assist": QColor(255, 152, 0), # Orange
"auto": QColor(76, 175, 80) # Vert
}
if mode in mode_colors:
mode_item.setForeground(QBrush(mode_colors[mode]))
self.tasks_table.setItem(row, 1, mode_item)
# Confiance
confidence = task.get("confidence_score", 0.0)
confidence_item = QTableWidgetItem(f"{confidence * 100:.1f}%")
confidence_item.setTextAlignment(Qt.AlignCenter)
# Couleur selon la confiance
if confidence >= 0.95:
confidence_item.setForeground(QBrush(QColor(76, 175, 80))) # Vert
elif confidence >= 0.85:
confidence_item.setForeground(QBrush(QColor(255, 152, 0))) # Orange
else:
confidence_item.setForeground(QBrush(QColor(244, 67, 54))) # Rouge
self.tasks_table.setItem(row, 2, confidence_item)
# Observations
obs_count = task.get("observation_count", 0)
obs_item = QTableWidgetItem(str(obs_count))
obs_item.setTextAlignment(Qt.AlignCenter)
self.tasks_table.setItem(row, 3, obs_item)
# Concordance
concordance = task.get("concordance_rate", 0.0)
concordance_item = QTableWidgetItem(f"{concordance * 100:.1f}%")
concordance_item.setTextAlignment(Qt.AlignCenter)
# Couleur selon la concordance
if concordance >= 0.95:
concordance_item.setForeground(QBrush(QColor(76, 175, 80))) # Vert
elif concordance >= 0.85:
concordance_item.setForeground(QBrush(QColor(255, 152, 0))) # Orange
else:
concordance_item.setForeground(QBrush(QColor(244, 67, 54))) # Rouge
self.tasks_table.setItem(row, 4, concordance_item)
# Corrections
corrections = task.get("correction_count", 0)
corrections_item = QTableWidgetItem(str(corrections))
corrections_item.setTextAlignment(Qt.AlignCenter)
self.tasks_table.setItem(row, 5, corrections_item)
# Taux de correction
correction_rate = task.get("correction_rate", 0.0)
correction_rate_item = QTableWidgetItem(f"{correction_rate * 100:.1f}%")
correction_rate_item.setTextAlignment(Qt.AlignCenter)
# Couleur selon le taux de correction (inverse: moins c'est mieux)
if correction_rate <= 0.03:
correction_rate_item.setForeground(QBrush(QColor(76, 175, 80))) # Vert
elif correction_rate <= 0.05:
correction_rate_item.setForeground(QBrush(QColor(255, 152, 0))) # Orange
else:
correction_rate_item.setForeground(QBrush(QColor(244, 67, 54))) # Rouge
self.tasks_table.setItem(row, 6, correction_rate_item)
# Dernière exécution
last_exec = task.get("last_execution", "")
if last_exec:
try:
dt = datetime.fromisoformat(last_exec)
last_exec_text = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
last_exec_text = last_exec
else:
last_exec_text = "Jamais"
last_exec_item = QTableWidgetItem(last_exec_text)
self.tasks_table.setItem(row, 7, last_exec_item)
def get_filtered_tasks(self) -> List[Dict[str, Any]]:
"""
Obtenir la liste des tâches filtrées selon les critères
Returns:
Liste de tâches filtrées
"""
filtered = list(self.tasks_data.values())
# Filtre de recherche
search_text = self.search_input.text().lower()
if search_text:
filtered = [
task for task in filtered
if search_text in task.get("task_name", "").lower() or
search_text in task.get("task_id", "").lower()
]
# Filtre de mode
mode_filter = self.mode_filter.currentText()
if mode_filter != "Tous":
mode_map = {
"Shadow": "shadow",
"Assisté": "assist",
"Autopilot": "auto"
}
target_mode = mode_map.get(mode_filter)
if target_mode:
filtered = [
task for task in filtered
if task.get("mode") == target_mode
]
# Trier par dernière exécution (plus récent en premier)
filtered.sort(
key=lambda t: t.get("last_execution", ""),
reverse=True
)
return filtered
def apply_filters(self):
"""Appliquer les filtres et mettre à jour le tableau"""
self.update_table()
def on_task_double_clicked(self, item):
"""
Gestionnaire de double-clic sur une tâche
Args:
item: Élément de tableau cliqué
"""
row = item.row()
task_id_item = self.tasks_table.item(row, 0)
if task_id_item:
task_id = task_id_item.data(Qt.UserRole)
if task_id and task_id in self.tasks_data:
self.show_task_details(task_id)
self.task_selected.emit(task_id)
def show_task_details(self, task_id: str):
"""
Afficher les détails d'une tâche dans une boîte de dialogue
Args:
task_id: ID de la tâche
"""
if task_id not in self.tasks_data:
return
task = self.tasks_data[task_id]
details = f"""
<h3>Détails de la Tâche</h3>
<p><b>ID:</b> {task.get('task_id', '')}</p>
<p><b>Nom:</b> {task.get('task_name', '')}</p>
<p><b>Mode:</b> {task.get('mode', '')}</p>
<p><b>Observations:</b> {task.get('observation_count', 0)}</p>
<p><b>Confiance:</b> {task.get('confidence_score', 0) * 100:.1f}%</p>
<p><b>Concordance:</b> {task.get('concordance_rate', 0) * 100:.1f}%</p>
<p><b>Corrections:</b> {task.get('correction_count', 0)}</p>
<p><b>Taux de correction:</b> {task.get('correction_rate', 0) * 100:.1f}%</p>
<p><b>Dernière exécution:</b> {task.get('last_execution', 'Jamais')}</p>
"""
msg_box = QMessageBox(self)
msg_box.setWindowTitle("Détails de la Tâche")
msg_box.setTextFormat(Qt.RichText)
msg_box.setText(details)
msg_box.setIcon(QMessageBox.Information)
msg_box.exec_()
def export_to_csv(self):
"""Exporter les données vers un fichier CSV"""
if not self.tasks_data:
QMessageBox.warning(
self,
"Aucune Donnée",
"Aucune donnée à exporter."
)
return
# Dialogue de sauvegarde
file_path, _ = QFileDialog.getSaveFileName(
self,
"Exporter vers CSV",
f"rpa_tasks_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
"CSV Files (*.csv)"
)
if not file_path:
return
try:
with open(file_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# En-têtes
writer.writerow([
"ID Tâche",
"Nom Tâche",
"Mode",
"Confiance (%)",
"Observations",
"Concordance (%)",
"Corrections",
"Taux Correction (%)",
"Dernière Exécution"
])
# Données
for task in self.get_filtered_tasks():
writer.writerow([
task.get("task_id", ""),
task.get("task_name", ""),
task.get("mode", ""),
f"{task.get('confidence_score', 0) * 100:.1f}",
task.get("observation_count", 0),
f"{task.get('concordance_rate', 0) * 100:.1f}",
task.get("correction_count", 0),
f"{task.get('correction_rate', 0) * 100:.1f}",
task.get("last_execution", "")
])
QMessageBox.information(
self,
"Export Réussi",
f"Données exportées vers:\n{file_path}"
)
self.logger.info(f"Données exportées vers CSV: {file_path}")
except Exception as e:
QMessageBox.critical(
self,
"Erreur d'Export",
f"Erreur lors de l'export CSV:\n{str(e)}"
)
self.logger.error(f"Erreur d'export CSV: {e}")
def export_to_json(self):
"""Exporter les données vers un fichier JSON"""
if not self.tasks_data:
QMessageBox.warning(
self,
"Aucune Donnée",
"Aucune donnée à exporter."
)
return
# Dialogue de sauvegarde
file_path, _ = QFileDialog.getSaveFileName(
self,
"Exporter vers JSON",
f"rpa_tasks_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
"JSON Files (*.json)"
)
if not file_path:
return
try:
export_data = {
"export_date": datetime.now().isoformat(),
"total_tasks": len(self.tasks_data),
"tasks": self.get_filtered_tasks()
}
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(export_data, f, indent=2, ensure_ascii=False)
QMessageBox.information(
self,
"Export Réussi",
f"Données exportées vers:\n{file_path}"
)
self.logger.info(f"Données exportées vers JSON: {file_path}")
except Exception as e:
QMessageBox.critical(
self,
"Erreur d'Export",
f"Erreur lors de l'export JSON:\n{str(e)}"
)
self.logger.error(f"Erreur d'export JSON: {e}")
def closeEvent(self, event):
"""Gestionnaire de fermeture"""
# Arrêter le timer de mise à jour
self.update_timer.stop()
event.accept()
@staticmethod
def show_dashboard(learning_manager=None, parent=None) -> 'SummaryDashboard':
"""
Méthode statique pour afficher le tableau de bord
Args:
learning_manager: Instance du gestionnaire d'apprentissage
parent: Widget parent (optionnel)
Returns:
Instance du tableau de bord
"""
dashboard = SummaryDashboard(learning_manager, parent)
dashboard.show()
return dashboard
if __name__ == "__main__":
"""Test du tableau de bord"""
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
# Créer un tableau de bord avec des données de test
dashboard = SummaryDashboard()
# Ajouter des données de test
test_tasks = [
{
"task_id": "task_001",
"task_name": "Ouvrir Facture",
"mode": "auto",
"confidence_score": 0.97,
"observation_count": 45,
"concordance_rate": 0.98,
"correction_count": 1,
"correction_rate": 0.022,
"last_execution": datetime.now().isoformat()
},
{
"task_id": "task_002",
"task_name": "Valider Commande",
"mode": "assist",
"confidence_score": 0.89,
"observation_count": 12,
"concordance_rate": 0.92,
"correction_count": 2,
"correction_rate": 0.167,
"last_execution": datetime.now().isoformat()
},
{
"task_id": "task_003",
"task_name": "Saisie Données Client",
"mode": "shadow",
"confidence_score": 0.65,
"observation_count": 3,
"concordance_rate": 0.67,
"correction_count": 0,
"correction_rate": 0.0,
"last_execution": datetime.now().isoformat()
}
]
for task in test_tasks:
dashboard.update_metrics(task["task_id"], task)
dashboard.show()
sys.exit(app.exec_())