#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script de correction des rapports existants Permet de: 1. Extraire correctement le JSON 2. Générer les fichiers CSV manquants 3. Analyser l'état des rapports générés """ import os import sys import json import glob import logging import argparse from datetime import datetime import traceback # Import des utilitaires try: from agents.utils.report_utils_bis import extraire_et_traiter_json except ImportError: print("ERREUR: Impossible d'importer les modules requis.") print("Assurez-vous d'exécuter ce script depuis le répertoire racine du projet.") sys.exit(1) # Configuration du logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger("fix_reports") def generate_csv_from_json(json_path, model_name=None): """ Génère un fichier CSV à partir du rapport JSON Args: json_path: Chemin vers le fichier JSON model_name: Nom du modèle pour le nom du fichier CSV (facultatif) """ try: # Charger le rapport JSON with open(json_path, 'r', encoding='utf-8') as f: rapport_data = json.load(f) # Extraire l'ID du ticket ticket_id = rapport_data.get("ticket_id", os.path.basename(json_path).split("_")[0]) # Extraire les échanges chronologie_echanges = rapport_data.get("chronologie_echanges", []) if not chronologie_echanges: logger.error(f"Aucun échange trouvé dans le fichier JSON: {json_path}") return False # Déterminer le nom du modèle if not model_name: # Essayer de l'extraire des métadonnées metadata = rapport_data.get("metadata", {}) model_name = metadata.get("model", "default").split(":")[0].lower() # Créer le nom du fichier CSV csv_path = os.path.join(os.path.dirname(json_path), f"{ticket_id}_{model_name}.csv") # Générer le CSV complet avec toutes les colonnes with open(csv_path, 'w', encoding='utf-8', newline='') as csvfile: # Écrire l'en-tête csvfile.write("Date,Émetteur,Type,Contenu\n") # Écrire les échanges for echange in chronologie_echanges: date = echange.get('date', '').replace(',', ' ') emetteur = echange.get('emetteur', '').replace(',', ' ') type_msg = echange.get('type', '').replace(',', ' ') contenu = echange.get('contenu', '').replace(',', ' ').replace('\n', ' ') csvfile.write(f'"{date}","{emetteur}","{type_msg}","{contenu}"\n') logger.info(f"Fichier CSV complet généré: {csv_path}") # Générer également une version simplifiée Q&R pour la compatibilité qr_csv_path = os.path.join(os.path.dirname(json_path), f"{ticket_id}_{model_name}_qr.csv") with open(qr_csv_path, 'w', encoding='utf-8', newline='') as csvfile: # Écrire l'en-tête csvfile.write("Question,Réponse\n") # Variables pour suivre les questions et réponses current_question = None # Parcourir les échanges et extraire les paires Q&R for echange in chronologie_echanges: emetteur = echange.get('emetteur', '').upper() type_msg = echange.get('type', '').lower() contenu = echange.get('contenu', '').replace(',', ' ').replace('\n', ' ') # Si c'est une question du client if emetteur == 'CLIENT' and (type_msg == 'question' or '?' in contenu): # Si une question précédente existe sans réponse, l'écrire avec une réponse vide if current_question: csvfile.write(f'"{current_question}",""\n') # Enregistrer la nouvelle question current_question = contenu # Si c'est une réponse ou un complément et qu'il y a une question en attente elif emetteur == 'SUPPORT' and current_question: # Préfixer la réponse pour la clarté formatted_response = f"[{type_msg.upper()}] {contenu}" # Écrire la paire question/réponse csvfile.write(f'"{current_question}","{formatted_response}"\n') current_question = None # Si une question reste sans réponse à la fin if current_question: csvfile.write(f'"{current_question}",""\n') logger.info(f"Fichier CSV Q&R simplifié généré: {qr_csv_path}") return csv_path except Exception as e: logger.error(f"Erreur lors de la génération du CSV pour {json_path}: {e}") traceback.print_exc() return False def fix_json_and_generate_csv(json_path): """ Corrige le JSON et génère le CSV manquant Args: json_path: Chemin vers le fichier JSON """ try: # Charger le JSON with open(json_path, 'r', encoding='utf-8') as f: rapport_data = json.load(f) # Extraire le rapport complet rapport_complet = rapport_data.get("rapport_complet", "") if not rapport_complet: logger.error(f"Aucun rapport complet trouvé dans {json_path}") return False # Extraire et traiter le JSON _, echanges_json, _ = extraire_et_traiter_json(rapport_complet) # Vérifier si des échanges ont été extraits if not echanges_json or "chronologie_echanges" not in echanges_json: logger.error(f"Aucun échange extrait du rapport {json_path}") return False # Mettre à jour le JSON avec les échanges extraits rapport_data["chronologie_echanges"] = echanges_json.get("chronologie_echanges", []) # Sauvegarder le JSON corrigé corrected_json_path = json_path.replace(".json", "_corrige.json") with open(corrected_json_path, 'w', encoding='utf-8') as f: json.dump(rapport_data, f, ensure_ascii=False, indent=2) logger.info(f"Rapport JSON corrigé sauvegardé: {corrected_json_path}") # Extraire le nom du modèle metadata = rapport_data.get("metadata", {}) model_name = metadata.get("model", "unknown").split(":")[0].lower() # Générer le CSV csv_path = generate_csv_from_json(corrected_json_path, model_name) if csv_path: logger.info(f"CSV généré avec succès: {csv_path}") return True else: logger.error("Échec de la génération du CSV") return False except Exception as e: logger.error(f"Erreur lors de la correction du JSON {json_path}: {e}") traceback.print_exc() return False def analyze_report(md_path): """ Analyse un rapport Markdown pour vérifier la présence des sections importantes Args: md_path: Chemin vers le fichier Markdown """ try: # Lire le fichier with open(md_path, 'r', encoding='utf-8') as f: content = f.read() # Vérifier les sections has_table = "| Date | " in content or "| Question | Réponse |" in content has_details = "## Détails des analyses" in content # Vérifier la présence de la section synthèse globale (avec variations possibles) has_synthese = any([ "## Synthèse globale des analyses d'images" in content, "## 3.1 Synthèse globale des analyses d'images" in content, "##3.1 Synthèse globale des analyses d'images" in content, "## Synthèse globale" in content ]) # Vérifier si le fichier CSV existe report_dir = os.path.dirname(md_path) ticket_basename = os.path.basename(md_path).split('_')[0] csv_files = [f for f in os.listdir(report_dir) if f.endswith('.csv') and ticket_basename in f] has_csv = len(csv_files) > 0 # Afficher les résultats print(f"\nAnalyse du rapport: {md_path}") print(f"- Tableau des échanges: {'Présent ✅' if has_table else 'MANQUANT ❌'}") print(f"- Détails des analyses: {'Présent ✅' if has_details else 'MANQUANT ❌'}") print(f"- Synthèse globale: {'Présent ✅' if has_synthese else 'MANQUANT ❌'}") if has_csv: print(f"- Fichier CSV: Présent ✅ ({', '.join(csv_files)})") else: print(f"- Fichier CSV: MANQUANT ❌") return { "md_path": md_path, "has_table": has_table, "has_details": has_details, "has_synthese": has_synthese, "has_csv": has_csv, "csv_files": csv_files } except Exception as e: logger.error(f"Erreur lors de l'analyse du rapport {md_path}: {e}") traceback.print_exc() return None def find_reports(base_dir=".", ticket_id=None): """ Recherche tous les rapports générés dans le répertoire de base Args: base_dir: Répertoire de base ticket_id: Identifiant du ticket (optionnel) Returns: Liste de tuples (json_path, md_path) """ reports = [] # Motif de recherche des rapports if ticket_id: pattern = f"**/{ticket_id}/**/{ticket_id}_rapport_final.json" else: pattern = "**/T*/T*_rapport_final.json" # Rechercher les fichiers JSON json_files = glob.glob(os.path.join(base_dir, pattern), recursive=True) for json_path in json_files: # Trouver le fichier Markdown correspondant md_path = json_path.replace(".json", ".md") if os.path.exists(md_path): reports.append((json_path, md_path)) return reports def main(): parser = argparse.ArgumentParser(description="Outil de correction des rapports existants") parser.add_argument("--dir", "-d", default=".", help="Répertoire de base contenant les rapports") parser.add_argument("--ticket", "-t", help="ID du ticket à corriger (ex: T0101)") parser.add_argument("--analyze", "-a", action="store_true", help="Analyser les rapports sans les corriger") parser.add_argument("--fix", "-f", action="store_true", help="Corriger les rapports") args = parser.parse_args() # Rechercher les rapports reports = find_reports(args.dir, args.ticket) if not reports: print(f"Aucun rapport trouvé dans {args.dir}" + (f" pour le ticket {args.ticket}" if args.ticket else "")) return print(f"Trouvé {len(reports)} rapport(s)") # Analyser les rapports if args.analyze or not args.fix: analyze_results = [] for json_path, md_path in reports: result = analyze_report(md_path) if result: analyze_results.append(result) # Afficher un résumé if analyze_results: print("\nRésumé de l'analyse:") print(f"- Total des rapports: {len(analyze_results)}") print(f"- Rapports avec tableau: {sum(1 for r in analyze_results if r['has_table'])}") print(f"- Rapports avec détails: {sum(1 for r in analyze_results if r['has_details'])}") print(f"- Rapports avec synthèse: {sum(1 for r in analyze_results if r['has_synthese'])}") print(f"- Rapports avec CSV: {sum(1 for r in analyze_results if r['has_csv'])}") # Lister les rapports à corriger reports_to_fix = [r["md_path"] for r in analyze_results if not (r["has_table"] and r["has_csv"])] if reports_to_fix: print("\nRapports à corriger (manque tableau ou CSV):") for report in reports_to_fix: print(f"- {report}") # Corriger les rapports if args.fix: for json_path, md_path in reports: # Analyser pour voir s'il a besoin d'être corrigé result = analyze_report(md_path) if result and not (result["has_table"] and result["has_csv"]): print(f"\nCorrection du rapport {json_path}...") success = fix_json_and_generate_csv(json_path) if success: print(f"✅ Rapport corrigé avec succès") else: print(f"❌ Échec de la correction du rapport") else: print(f"\nLe rapport {json_path} n'a pas besoin d'être corrigé") if __name__ == "__main__": main()