mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-15 21:56:49 +01:00
431 lines
18 KiB
Python
431 lines
18 KiB
Python
#!/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 += "<details>\n<summary>Analyse détaillée de l'image</summary>\n\n"
|
|
markdown += "```\n" + analyse_detail + "\n```\n\n"
|
|
markdown += "</details>\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 += "<details>\n<summary>Voir le résultat de l'analyse de ticket</summary>\n\n"
|
|
markdown += "```\n" + str(rapport_data["ticket_analyse"]) + "\n```\n\n"
|
|
markdown += "</details>\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"<details>\n<summary>Prompt de {agent_name}</summary>\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 += "</details>\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"""<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Rapport d'analyse de ticket</title>
|
|
<style>
|
|
body {{ font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; max-width: 1200px; margin: 0 auto; }}
|
|
h1 {{ color: #2c3e50; border-bottom: 2px solid #eee; padding-bottom: 10px; }}
|
|
h2 {{ color: #3498db; margin-top: 30px; }}
|
|
h3 {{ color: #2980b9; }}
|
|
h4 {{ color: #16a085; }}
|
|
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
|
|
th, td {{ padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; }}
|
|
th {{ background-color: #f2f2f2; }}
|
|
tr:hover {{ background-color: #f5f5f5; }}
|
|
code, pre {{ background: #f8f8f8; border: 1px solid #ddd; border-radius: 3px; padding: 10px; overflow-x: auto; }}
|
|
details {{ margin: 15px 0; }}
|
|
summary {{ cursor: pointer; font-weight: bold; color: #2980b9; }}
|
|
.status {{ color: #e74c3c; font-weight: bold; }}
|
|
hr {{ border: 0; height: 1px; background: #eee; margin: 30px 0; }}
|
|
em {{ color: #7f8c8d; }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
{html_content}
|
|
</body>
|
|
</html>"""
|
|
|
|
# É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'<h1>\1</h1>', html, flags=re.MULTILINE)
|
|
html = re.sub(r'^## (.*?)$', r'<h2>\1</h2>', html, flags=re.MULTILINE)
|
|
html = re.sub(r'^### (.*?)$', r'<h3>\1</h3>', html, flags=re.MULTILINE)
|
|
html = re.sub(r'^#### (.*?)$', r'<h4>\1</h4>', html, flags=re.MULTILINE)
|
|
|
|
# Emphase
|
|
html = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', html)
|
|
html = re.sub(r'\*(.*?)\*', r'<em>\1</em>', html)
|
|
|
|
# Lists
|
|
html = re.sub(r'^- (.*?)$', r'<li>\1</li>', html, flags=re.MULTILINE)
|
|
|
|
# Paragraphes
|
|
html = re.sub(r'([^\n])\n([^\n])', r'\1<br>\2', html)
|
|
html = re.sub(r'\n\n', r'</p><p>', html)
|
|
|
|
# Tables simplifiées (sans analyser la structure)
|
|
html = re.sub(r'\| (.*?) \|', r'<td>\1</td>', html)
|
|
|
|
# Code blocks
|
|
html = re.sub(r'```(.*?)```', r'<pre><code>\1</code></pre>', html, flags=re.DOTALL)
|
|
|
|
# Details/Summary
|
|
html = re.sub(r'<details>(.*?)<summary>(.*?)</summary>(.*?)</details>',
|
|
r'<details>\1<summary>\2</summary>\3</details>',
|
|
html, flags=re.DOTALL)
|
|
|
|
# Envelopper dans des balises paragraphe
|
|
html = f"<p>{html}</p>"
|
|
|
|
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}") |