mirror of
https://github.com/Ladebeze66/llm_ticket3.git
synced 2025-12-15 22:06:50 +01:00
324 lines
14 KiB
Python
324 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Fonctions utilitaires pour nettoyer le HTML et formater les dates.
|
|
Version simplifiée et robuste: ignore les lignes problématiques.
|
|
"""
|
|
|
|
import re
|
|
from datetime import datetime
|
|
|
|
def clean_html(html_content, is_description=False):
|
|
"""
|
|
Nettoie le contenu HTML pour le Markdown en identifiant et ignorant les parties problématiques.
|
|
|
|
Args:
|
|
html_content (str): Contenu HTML à nettoyer
|
|
is_description (bool): Indique si le contenu est une description de ticket
|
|
|
|
Returns:
|
|
str: Texte nettoyé
|
|
"""
|
|
if not html_content:
|
|
return "*Contenu vide*"
|
|
|
|
# 0. PRÉVENIR LES DOUBLONS - Détecter et supprimer les messages dupliqués
|
|
# Cette étape permet d'éliminer les messages qui apparaissent en double
|
|
|
|
# D'abord, nettoyer le HTML pour comparer les sections de texte réel
|
|
cleaned_for_comparison = pre_clean_html(html_content)
|
|
|
|
# Détection des doublons basée sur les premières lignes
|
|
# Si le même début apparaît deux fois, ne garder que jusqu'à la première occurrence
|
|
first_paragraph = ""
|
|
for line in cleaned_for_comparison.split('\n'):
|
|
if len(line.strip()) > 10: # Ignorer les lignes vides ou trop courtes
|
|
first_paragraph = line.strip()
|
|
break
|
|
|
|
if first_paragraph and first_paragraph in cleaned_for_comparison[len(first_paragraph):]:
|
|
# Le premier paragraphe apparaît deux fois - couper au début de la deuxième occurrence
|
|
pos = cleaned_for_comparison.find(first_paragraph, len(first_paragraph))
|
|
if pos > 0:
|
|
# Utiliser cette position pour couper le contenu original
|
|
html_content = html_content[:pos].strip()
|
|
|
|
# Diviser le contenu en sections potentielles (souvent séparées par des lignes vides doubles)
|
|
sections = re.split(r'\n\s*\n\s*\n', html_content)
|
|
|
|
# Si le contenu a plusieurs sections, ne garder que la première section significative
|
|
if len(sections) > 1:
|
|
# Rechercher la première section qui contient du texte significatif (non des en-têtes/métadonnées)
|
|
significant_content = ""
|
|
for section in sections:
|
|
# Ignorer les sections très courtes ou qui ressemblent à des en-têtes
|
|
if len(section.strip()) > 50 and not re.search(r'^(?:Subject|Date|From|To|Cc|Objet|De|À|Copie à):', section, re.IGNORECASE):
|
|
significant_content = section
|
|
break
|
|
|
|
# Si on a trouvé une section significative, l'utiliser comme contenu
|
|
if significant_content:
|
|
html_content = significant_content
|
|
|
|
# 1. CAS SPÉCIAUX - Traités en premier avec leurs propres règles
|
|
|
|
# 1.1. Traitement spécifique pour les descriptions
|
|
if is_description:
|
|
# Suppression complète des balises HTML de base
|
|
content = pre_clean_html(html_content)
|
|
content = re.sub(r'\n\s*\n', '\n\n', content)
|
|
return content.strip()
|
|
|
|
# 1.2. Traitement des messages transférés avec un pattern spécifique
|
|
if "\\-------- Message transféré --------" in html_content or "-------- Courriel original --------" in html_content:
|
|
# Essayer d'extraire le contenu principal du message transféré
|
|
match = re.search(r'(?:De|From|Copie à|Cc)\s*:.*?\n\s*\n(.*?)(?=\n\s*(?:__+|--+|==+|\\\\|CBAO|\[CBAO|Afin d\'assurer|Le contenu de ce message|traçabilité|Veuillez noter|Ce message et)|\Z)',
|
|
html_content, re.DOTALL | re.IGNORECASE)
|
|
if match:
|
|
return match.group(1).strip()
|
|
else:
|
|
# Essayer une autre approche si la première échoue
|
|
match = re.search(r'Bonjour.*?(?=\n\s*(?:__+|--+|==+|\\\\|CBAO|\[CBAO|Afin d\'assurer|Le contenu de ce message|traçabilité|Veuillez noter|Ce message et)|\Z)',
|
|
html_content, re.DOTALL)
|
|
if match:
|
|
return match.group(0).strip()
|
|
|
|
# 1.3. Traitement des notifications d'appel
|
|
if "Notification d'appel" in html_content:
|
|
match = re.search(r'(?:Sujet d\'appel:[^\n]*\n[^\n]*\n[^\n]*\n[^\n]*\n)[^\n]*\n[^\n]*([^|]+)', html_content, re.DOTALL)
|
|
if match:
|
|
message_content = match.group(1).strip()
|
|
# Construire un message formaté avec les informations essentielles
|
|
infos = {}
|
|
date_match = re.search(r'Date:.*?\|(.*?)(?:\n|$)', html_content)
|
|
appelant_match = re.search(r'\*\*Appel de:\*\*.*?\|(.*?)(?:\n|$)', html_content)
|
|
telephone_match = re.search(r'Téléphone principal:.*?\|(.*?)(?:\n|$)', html_content)
|
|
mobile_match = re.search(r'Mobile:.*?\|(.*?)(?:\n|$)', html_content)
|
|
sujet_match = re.search(r'Sujet d\'appel:.*?\|(.*?)(?:\n|$)', html_content)
|
|
|
|
if date_match:
|
|
infos["date"] = date_match.group(1).strip()
|
|
if appelant_match:
|
|
infos["appelant"] = appelant_match.group(1).strip()
|
|
if telephone_match:
|
|
infos["telephone"] = telephone_match.group(1).strip()
|
|
if mobile_match:
|
|
infos["mobile"] = mobile_match.group(1).strip()
|
|
if sujet_match:
|
|
infos["sujet"] = sujet_match.group(1).strip()
|
|
|
|
# Construire le message formaté
|
|
formatted_message = f"**Notification d'appel**\n\n"
|
|
if "appelant" in infos:
|
|
formatted_message += f"De: {infos['appelant']}\n"
|
|
if "date" in infos:
|
|
formatted_message += f"Date: {infos['date']}\n"
|
|
if "telephone" in infos:
|
|
formatted_message += f"Téléphone: {infos['telephone']}\n"
|
|
if "mobile" in infos:
|
|
formatted_message += f"Mobile: {infos['mobile']}\n"
|
|
if "sujet" in infos:
|
|
formatted_message += f"Sujet: {infos['sujet']}\n\n"
|
|
|
|
formatted_message += f"Message: {message_content}"
|
|
|
|
return formatted_message
|
|
|
|
# 2. NOUVELLE APPROCHE SIMPLE - Filtrer les lignes problématiques
|
|
|
|
# 2.1. D'abord nettoyer le HTML
|
|
cleaned_content = pre_clean_html(html_content)
|
|
|
|
# 2.2. Diviser en lignes et filtrer les lignes problématiques
|
|
filtered_lines = []
|
|
|
|
# Liste des indicateurs de lignes problématiques
|
|
problematic_indicators = [
|
|
"http://", "https://", ".fr", ".com", "@",
|
|
"[", "]", "!/web/image/"
|
|
]
|
|
|
|
# Mémoriser l'indice de la ligne contenant "Cordialement" ou équivalent
|
|
signature_line_idx = -1
|
|
|
|
lines = cleaned_content.split('\n')
|
|
for i, line in enumerate(lines):
|
|
# Détecter la signature
|
|
if any(sig in line.lower() for sig in ["cordialement", "cdlt", "bien à vous", "salutation"]):
|
|
signature_line_idx = i
|
|
|
|
# Vérifier si la ligne contient un indicateur problématique
|
|
is_problematic = any(indicator in line for indicator in problematic_indicators)
|
|
|
|
# Si la ligne est très longue (plus de 200 caractères), la considérer comme problématique
|
|
if len(line) > 200:
|
|
is_problematic = True
|
|
|
|
# Ajouter la ligne seulement si elle n'est pas problématique
|
|
if not is_problematic:
|
|
filtered_lines.append(line)
|
|
|
|
# 2.3. Si on a trouvé une signature, ne garder que 2 lignes après maximum
|
|
if signature_line_idx >= 0:
|
|
filtered_lines = filtered_lines[:min(signature_line_idx + 3, len(filtered_lines))]
|
|
|
|
# 2.4. Recombiner les lignes filtrées
|
|
content = '\n'.join(filtered_lines)
|
|
|
|
# 2.5. Nettoyer les espaces et lignes vides
|
|
content = re.sub(r'\n{3,}', '\n\n', content)
|
|
content = content.strip()
|
|
|
|
# 2.6. VÉRIFICATION FINALE: S'assurer qu'il n'y a pas de duplication dans le contenu final
|
|
# Si le même paragraphe apparaît deux fois, ne garder que jusqu'à la première occurrence
|
|
lines = content.split('\n')
|
|
unique_lines = []
|
|
seen_paragraphs = set()
|
|
|
|
for line in lines:
|
|
clean_line = line.strip()
|
|
# Ne traiter que les lignes non vides et assez longues pour être significatives
|
|
if clean_line and len(clean_line) > 10:
|
|
if clean_line in seen_paragraphs:
|
|
# On a déjà vu cette ligne, c'est probablement une duplication
|
|
# Arrêter le traitement ici
|
|
break
|
|
seen_paragraphs.add(clean_line)
|
|
unique_lines.append(line)
|
|
|
|
content = '\n'.join(unique_lines)
|
|
|
|
# Résultat final
|
|
if not content or len(content.strip()) < 10:
|
|
return "*Contenu non extractible*"
|
|
|
|
return content
|
|
|
|
def pre_clean_html(html_content):
|
|
"""
|
|
Effectue un nettoyage préliminaire du HTML en préservant la structure et le formatage basique.
|
|
"""
|
|
# Remplacer les balises de paragraphe et saut de ligne par des sauts de ligne
|
|
content = re.sub(r'<br\s*/?>|<p[^>]*>|</p>|<div[^>]*>|</div>', '\n', html_content)
|
|
|
|
# Préserver le formatage de base (gras, italique, etc.)
|
|
content = re.sub(r'<(?:b|strong)>(.*?)</(?:b|strong)>', r'**\1**', content)
|
|
content = re.sub(r'<(?:i|em)>(.*?)</(?:i|em)>', r'*\1*', content)
|
|
|
|
# Transformer les listes
|
|
content = re.sub(r'<li>(.*?)</li>', r'- \1\n', content)
|
|
|
|
# Supprimer les balises HTML avec leurs attributs mais conserver le contenu
|
|
content = re.sub(r'<[^>]+>', '', content)
|
|
|
|
# Remplacer les entités HTML courantes
|
|
content = content.replace(' ', ' ')
|
|
content = content.replace('<', '<')
|
|
content = content.replace('>', '>')
|
|
content = content.replace('&', '&')
|
|
content = content.replace('"', '"')
|
|
|
|
# Nettoyer les espaces multiples
|
|
content = re.sub(r' {2,}', ' ', content)
|
|
|
|
# Nettoyer les sauts de ligne multiples (mais pas tous, pour préserver la structure)
|
|
content = re.sub(r'\n{3,}', '\n\n', content)
|
|
|
|
return content.strip()
|
|
|
|
def format_date(date_str):
|
|
"""
|
|
Formate une date ISO en format lisible.
|
|
"""
|
|
if not date_str:
|
|
return ""
|
|
|
|
try:
|
|
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
|
return dt.strftime("%d/%m/%Y %H:%M:%S")
|
|
except (ValueError, TypeError):
|
|
return date_str
|
|
|
|
if __name__ == "__main__":
|
|
# Tests
|
|
html = """<p>Bonjour,</p>
|
|
<p>Voici un message avec <b>du HTML</b> et une signature.</p>
|
|
<p>Cordialement,</p>
|
|
<p>John Doe</p>
|
|
<p>Support technique</p>
|
|
<p>Afin d'assurer une meilleure traçabilité et vous garantir une prise en charge optimale,
|
|
nous vous invitons à envoyer vos demandes d'assistance technique à support@exemple.fr</p>
|
|
<p></p>
|
|
"""
|
|
|
|
cleaned = clean_html(html)
|
|
print("HTML nettoyé :\n", cleaned)
|
|
|
|
# Test avec un message transféré
|
|
forwarded = """\\-------- Message transféré -------- Sujet : | Test message
|
|
---|---
|
|
Date : | Mon, 30 Mar 2020 11:18:20 +0200
|
|
De : | [test@example.com](mailto:test@example.com)
|
|
Pour : | John Doe [](mailto:john@example.com)
|
|
Copie à : | [other@example.com](mailto:other@example.com)
|
|
|
|
Bonjour John,
|
|
|
|
Voici un message de test.
|
|
|
|
Cordialement,
|
|
Test User
|
|
|
|
__________________________________________________________________ Ce message et toutes les pièces jointes sont confidentiels et établis à l'intention exclusive de ses destinataires. __________________________________________________________________"""
|
|
|
|
cleaned_forwarded = clean_html(forwarded)
|
|
print("\nMessage transféré nettoyé :\n", cleaned_forwarded)
|
|
|
|
# Test avec le cas problématique du ticket T0282
|
|
test_t0282 = """Bonjour,
|
|
|
|
Je reviens vers vous pour savoir si vous souhaitez toujours renommer le numéro d'identification de certaines formules dans BCN ou si vous avez trouvé une solution alternative ?
|
|
|
|
En vous remerciant par avance, je reste à votre disposition pour tout complément d'information.
|
|
|
|
Cordialement.
|
|
|
|
**Youness BENDEQ**
|
|
|
|
[
|
|
|
|
Affin d'assurer une meilleure traçabilité et vous garantir une prise en charge optimale, nous vous invitons à envoyer vos demandes d'assistance technique à support@cbao.fr Notre service est ouvert du lundi au vendredi de 9h à 12h et de 14h à 18h. Dès réception, un technicien prendra en charge votre demande et au besoin vous rappellera."""
|
|
|
|
cleaned_t0282 = clean_html(test_t0282)
|
|
print("\nTest ticket T0282 nettoyé :\n", cleaned_t0282)
|
|
|
|
# Test avec le cas problématique de bas de page avec formatage markdown
|
|
test_cbao_markdown = """Bonjour,
|
|
|
|
Voici un message de test pour vérifier la suppression des bas de page CBAO.
|
|
|
|
Cordialement,
|
|
Jean Dupont
|
|
|
|
[ CBAO S.A.R.L. ](https://example.com/link) .
|
|
|
|
 """
|
|
|
|
cleaned_markdown = clean_html(test_cbao_markdown)
|
|
print("\nTest avec formatage Markdown CBAO nettoyé :\n", cleaned_markdown)
|
|
|
|
# Test avec le cas exact du rapport
|
|
test_rapport = """Bonjour,
|
|
|
|
Voici un message de test.
|
|
|
|
Cordialement,
|
|
Pierre Martin
|
|
|
|
Envoyé par [ CBAO S.A.R.L. ](https://ciibcee.r.af.d.sendibt2.com/tr/cl/h2uBsi9hBosNYeSHMsPH47KAmufMTuNZjreF6M_tfRE63xzft8fwSbEQNb0aYIor74WQB5L6TF4kR9szVpQnalHFa3PUn_0jeLw42JNzIwsESwVlYad_3xCC1xi7qt3-dQ7i_Rt62MG217XgidnJxyNVcXWaWG5B75sB0GoqJq13IZc-hQ) .
|
|
|
|
 """
|
|
|
|
cleaned_rapport = clean_html(test_rapport)
|
|
print("\nTest avec cas exact du rapport nettoyé :\n", cleaned_rapport) |