#!/usr/bin/env python3 """ Module pour formater les rapports à partir des fichiers JSON générés par l'AgentReportGenerator. Ce module prend en entrée un fichier JSON contenant les analyses et génère différents formats de sortie (Markdown, HTML, etc.) sans utiliser de LLM. """ import os import json import argparse import sys import re from datetime import datetime from typing import Dict, List, Any, Optional, Tuple def generate_markdown_report(json_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]: """ Génère un rapport au format Markdown à partir d'un fichier JSON. Args: json_path: Chemin vers le fichier JSON contenant les données du rapport output_path: Chemin de sortie pour le fichier Markdown (facultatif) Returns: Tuple (succès, chemin du fichier généré ou message d'erreur) """ try: # Lire le fichier JSON with open(json_path, "r", encoding="utf-8") as f: rapport_data = json.load(f) # Si le chemin de sortie n'est pas spécifié, le créer à partir du chemin d'entrée if not output_path: # Remplacer l'extension JSON par MD output_path = os.path.splitext(json_path)[0] + ".md" # Générer le contenu Markdown markdown_content = _generate_markdown_content(rapport_data) # Écrire le contenu dans le fichier de sortie with open(output_path, "w", encoding="utf-8") as f: f.write(markdown_content) print(f"Rapport Markdown généré avec succès: {output_path}") return True, output_path except Exception as e: error_message = f"Erreur lors de la génération du rapport Markdown: {str(e)}" print(error_message) return False, error_message def _generate_markdown_content(rapport_data: Dict) -> str: """ Génère le contenu Markdown à partir des données du rapport. Args: rapport_data: Dictionnaire contenant les données du rapport Returns: Contenu Markdown """ ticket_id = rapport_data.get("ticket_id", "") timestamp = rapport_data.get("metadata", {}).get("timestamp", "") generation_date = rapport_data.get("metadata", {}).get("generation_date", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) # Récupérer les infos sur le workflow et les agents workflow = rapport_data.get("workflow", {}).get("etapes", []) agents_info = rapport_data.get("metadata", {}).get("agents", {}) # Entête du document markdown = f"# Rapport d'analyse du ticket #{ticket_id}\n\n" markdown += f"*Généré le: {generation_date}*\n\n" # 1. Résumé exécutif if "resume" in rapport_data and rapport_data["resume"]: markdown += "## Résumé du problème\n\n" markdown += rapport_data["resume"] + "\n\n" # 2. Chronologie des échanges (tableau) markdown += "## Chronologie des échanges client/support\n\n" markdown += "_Agent utilisé: AgentTicketAnalyser_\n\n" if "chronologie_echanges" in rapport_data and rapport_data["chronologie_echanges"]: # Créer un tableau pour les échanges markdown += "| Date | Émetteur | Type | Contenu | Statut |\n" markdown += "|------|---------|------|---------|--------|\n" # Prétraitement pour détecter les questions sans réponse questions_sans_reponse = {} echanges = rapport_data["chronologie_echanges"] for i, echange in enumerate(echanges): if echange.get("type", "").lower() == "question" and echange.get("emetteur", "").lower() == "client": has_response = False # Vérifier si la question a une réponse for j in range(i+1, len(echanges)): next_echange = echanges[j] if next_echange.get("type", "").lower() == "réponse" and next_echange.get("emetteur", "").lower() == "support": has_response = True break questions_sans_reponse[i] = not has_response # Générer les lignes du tableau for i, echange in enumerate(echanges): date = echange.get("date", "-") emetteur = echange.get("emetteur", "-") type_msg = echange.get("type", "-") contenu = echange.get("contenu", "-") # Ajouter un statut pour les questions sans réponse statut = "" if emetteur.lower() == "client" and type_msg.lower() == "question" and questions_sans_reponse.get(i, False): statut = "**Sans réponse**" markdown += f"| {date} | {emetteur} | {type_msg} | {contenu} | {statut} |\n" # Ajouter une note si aucune réponse du support n'a été trouvée if not any(echange.get("emetteur", "").lower() == "support" for echange in echanges): markdown += "\n**Note: Aucune réponse du support n'a été trouvée dans ce ticket.**\n\n" else: markdown += "*Aucun échange détecté dans le ticket.*\n\n" # 3. Récapitulatif des questions et réponses if "tableau_questions_reponses" in rapport_data and rapport_data["tableau_questions_reponses"]: markdown += "## Résumé des questions et réponses\n\n" markdown += rapport_data["tableau_questions_reponses"] + "\n\n" # 4. Analyse des images markdown += "## Analyse des images\n\n" markdown += "_Agent utilisé: AgentImageAnalyser_\n\n" if "images_analyses" in rapport_data and rapport_data["images_analyses"]: images_list = rapport_data["images_analyses"] if not images_list: markdown += "*Aucune image pertinente n'a été identifiée.*\n\n" else: for i, img_data in enumerate(images_list, 1): image_name = img_data.get("image_name", f"Image {i}") sorting_info = img_data.get("sorting_info", {}) reason = sorting_info.get("reason", "Non spécifiée") markdown += f"### Image {i}: {image_name}\n\n" # Raison de la pertinence if reason: markdown += f"**Raison de la pertinence**: {reason}\n\n" # Ajouter l'analyse détaillée dans une section dépliable analyse_detail = img_data.get("analyse", "Aucune analyse disponible") if analyse_detail: markdown += "
\nAnalyse détaillée de l'image\n\n" markdown += "```\n" + analyse_detail + "\n```\n\n" markdown += "
\n\n" else: markdown += "*Aucune image pertinente n'a été analysée.*\n\n" # 5. Diagnostic technique if "diagnostic" in rapport_data and rapport_data["diagnostic"]: markdown += "## Diagnostic technique\n\n" markdown += "_Agent utilisé: AgentReportGenerator_\n\n" markdown += rapport_data["diagnostic"] + "\n\n" # Séparateur pour la section technique markdown += "---\n\n" # Flux de traitement (workflow) markdown += "# Flux de traitement du ticket\n\n" if workflow: markdown += "Le traitement de ce ticket a suivi les étapes suivantes :\n\n" for etape in workflow: numero = etape.get("numero", "") nom = etape.get("nom", "") agent = etape.get("agent", "") description = etape.get("description", "") markdown += f"### Étape {numero}: {nom}\n\n" markdown += f"**Agent**: {agent}\n\n" markdown += f"{description}\n\n" # Ajouter des détails sur l'agent selon son type if agent == "AgentTicketAnalyser" and "ticket_analyse" in rapport_data: markdown += "
\nVoir le résultat de l'analyse de ticket\n\n" markdown += "```\n" + str(rapport_data["ticket_analyse"]) + "\n```\n\n" markdown += "
\n\n" elif agent == "AgentImageSorter": markdown += f"Images analysées: {rapport_data.get('statistiques', {}).get('total_images', 0)}\n" markdown += f"Images pertinentes identifiées: {rapport_data.get('statistiques', {}).get('images_pertinentes', 0)}\n\n" else: markdown += "Informations sur le flux de traitement non disponibles.\n\n" # Informations techniques et métadonnées markdown += "# Informations techniques\n\n" # Statistiques statistiques = rapport_data.get("statistiques", {}) metadata = rapport_data.get("metadata", {}) markdown += "## Statistiques de traitement\n\n" markdown += f"- **Images analysées**: {statistiques.get('total_images', 0)}\n" markdown += f"- **Images pertinentes**: {statistiques.get('images_pertinentes', 0)}\n" if "generation_time" in statistiques: markdown += f"- **Temps de génération**: {statistiques['generation_time']:.2f} secondes\n" # Information sur les agents et les modèles utilisés markdown += "\n## Agents et modèles utilisés\n\n" # Agent Report Generator if "report_generator" in agents_info: report_generator = agents_info["report_generator"] markdown += "### Agent de génération de rapport\n\n" markdown += f"- **Modèle**: {report_generator.get('model', 'Non spécifié')}\n" markdown += f"- **Version du prompt**: {report_generator.get('prompt_version', 'Non spécifiée')}\n" markdown += f"- **Température**: {report_generator.get('temperature', 'Non spécifiée')}\n" markdown += f"- **Top_p**: {report_generator.get('top_p', 'Non spécifié')}\n\n" # Agent Ticket Analyser if "ticket_analyser" in agents_info: ticket_analyser = agents_info["ticket_analyser"] markdown += "### Agent d'analyse de ticket\n\n" if "model_info" in ticket_analyser: markdown += f"- **Modèle**: {ticket_analyser['model_info'].get('name', 'Non spécifié')}\n\n" elif "model" in ticket_analyser: markdown += f"- **Modèle**: {ticket_analyser.get('model', 'Non spécifié')}\n\n" # Agent Image Sorter if "image_sorter" in agents_info: image_sorter = agents_info["image_sorter"] markdown += "### Agent de tri d'images\n\n" if "model_info" in image_sorter: markdown += f"- **Modèle**: {image_sorter['model_info'].get('name', 'Non spécifié')}\n\n" elif "model" in image_sorter: markdown += f"- **Modèle**: {image_sorter.get('model', 'Non spécifié')}\n\n" # Agent Image Analyser if "image_analyser" in agents_info: image_analyser = agents_info["image_analyser"] markdown += "### Agent d'analyse d'images\n\n" if "model_info" in image_analyser: markdown += f"- **Modèle**: {image_analyser['model_info'].get('name', 'Non spécifié')}\n\n" elif "model" in image_analyser: markdown += f"- **Modèle**: {image_analyser.get('model', 'Non spécifié')}\n\n" # Prompts utilisés par les agents if "prompts_utilisés" in rapport_data and rapport_data["prompts_utilisés"]: markdown += "\n## Prompts utilisés par les agents\n\n" prompts = rapport_data["prompts_utilisés"] for agent, prompt in prompts.items(): agent_name = { "rapport_generator": "Agent de génération de rapport", "ticket_analyser": "Agent d'analyse de ticket", "image_sorter": "Agent de tri d'images", "image_analyser": "Agent d'analyse d'images" }.get(agent, agent) markdown += f"
\nPrompt de {agent_name}\n\n" # Si le prompt est trop long, le tronquer pour éviter des rapports trop volumineux if len(prompt) > 2000: debut = prompt[:1000].strip() fin = prompt[-1000:].strip() prompt_tronque = f"{debut}\n\n[...]\n\n{fin}" markdown += f"```\n{prompt_tronque}\n```\n\n" else: markdown += f"```\n{prompt}\n```\n\n" markdown += "
\n\n" return markdown def generate_html_report(json_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]: """ Génère un rapport au format HTML à partir d'un fichier JSON. Args: json_path: Chemin vers le fichier JSON contenant les données du rapport output_path: Chemin de sortie pour le fichier HTML (facultatif) Returns: Tuple (succès, chemin du fichier généré ou message d'erreur) """ try: # Générer d'abord le Markdown success, md_path_or_error = generate_markdown_report(json_path, None) if not success: return False, md_path_or_error # Lire le contenu Markdown with open(md_path_or_error, "r", encoding="utf-8") as f: markdown_content = f.read() # Si le chemin de sortie n'est pas spécifié, le créer à partir du chemin d'entrée if not output_path: # Remplacer l'extension JSON par HTML output_path = os.path.splitext(json_path)[0] + ".html" # Conversion Markdown → HTML (avec gestion de l'absence de mistune) html_content = _simple_markdown_to_html(markdown_content) # Essayer d'utiliser mistune pour une meilleure conversion si disponible try: import mistune markdown = mistune.create_markdown(escape=False) html_content = markdown(markdown_content) print("Conversion HTML effectuée avec mistune") except ImportError: print("Module mistune non disponible, utilisation de la conversion HTML simplifiée") # Créer un HTML complet avec un peu de style html_page = f""" Rapport d'analyse de ticket {html_content} """ # Écrire le contenu dans le fichier de sortie with open(output_path, "w", encoding="utf-8") as f: f.write(html_page) print(f"Rapport HTML généré avec succès: {output_path}") return True, output_path except Exception as e: error_message = f"Erreur lors de la génération du rapport HTML: {str(e)}" print(error_message) return False, error_message def _simple_markdown_to_html(markdown_content: str) -> str: """ Convertit un contenu Markdown en HTML de façon simplifiée. Args: markdown_content: Contenu Markdown à convertir Returns: Contenu HTML """ html = markdown_content # Titres html = re.sub(r'^# (.*?)$', r'

\1

', html, flags=re.MULTILINE) html = re.sub(r'^## (.*?)$', r'

\1

', html, flags=re.MULTILINE) html = re.sub(r'^### (.*?)$', r'

\1

', html, flags=re.MULTILINE) html = re.sub(r'^#### (.*?)$', r'

\1

', html, flags=re.MULTILINE) # Emphase html = re.sub(r'\*\*(.*?)\*\*', r'\1', html) html = re.sub(r'\*(.*?)\*', r'\1', html) # Lists html = re.sub(r'^- (.*?)$', r'
  • \1
  • ', html, flags=re.MULTILINE) # Paragraphes html = re.sub(r'([^\n])\n([^\n])', r'\1
    \2', html) html = re.sub(r'\n\n', r'

    ', html) # Tables simplifiées (sans analyser la structure) html = re.sub(r'\| (.*?) \|', r'\1', html) # Code blocks html = re.sub(r'```(.*?)```', r'

    \1
    ', html, flags=re.DOTALL) # Details/Summary html = re.sub(r'
    (.*?)(.*?)(.*?)
    ', r'
    \1\2\3
    ', html, flags=re.DOTALL) # Envelopper dans des balises paragraphe html = f"

    {html}

    " return html def process_report(json_path: str, output_format: str = "markdown") -> None: """ Traite un rapport dans le format spécifié. Args: json_path: Chemin vers le fichier JSON contenant les données du rapport output_format: Format de sortie (markdown ou html) """ if output_format.lower() == "markdown": generate_markdown_report(json_path) elif output_format.lower() == "html": generate_html_report(json_path) else: print(f"Format non supporté: {output_format}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Formateur de rapports à partir de fichiers JSON") parser.add_argument("json_path", help="Chemin vers le fichier JSON contenant les données du rapport") parser.add_argument("--format", "-f", choices=["markdown", "html"], default="markdown", help="Format de sortie (markdown par défaut)") parser.add_argument("--output", "-o", help="Chemin de sortie pour le rapport (facultatif)") args = parser.parse_args() if args.format == "markdown": generate_markdown_report(args.json_path, args.output) elif args.format == "html": generate_html_report(args.json_path, args.output) else: print(f"Format non supporté: {args.format}")