File size: 15,836 Bytes
f92da22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
import os
import logging
from datetime import datetime
from typing import List
from docx import Document
from docx.shared import Inches
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.shared import RGBColor
from docx.oxml.shared import OxmlElement, qn

# Importer les classes du premier fichier
from template_matcher import TemplateMatcher, TemplateMatch

from dotenv import load_dotenv

# Charger les variables d'environnement
load_dotenv()

DB_PATH = os.getenv("TEMPLATE_DB_PATH", "templates/medical_templates.pkl")
OUTPUT_DIR = os.getenv("OUTPUT_DIR", "templates_remplis")

class TemplateGenerator:
    """Génère des templates médicaux remplis au format .doc"""
    
    def __init__(self):
        """Initialise le générateur de templates"""

        self.output_dir = OUTPUT_DIR
        self._create_output_directory()
        
        # Configuration du logging pour ce module
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - [GENERATOR] %(message)s'
        )
    
    def _create_output_directory(self):
        """Crée le répertoire de sortie s'il n'existe pas"""
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
            logging.info(f"📁 Répertoire de sortie créé: {self.output_dir}")

    def _add_custom_styles(self, doc: Document):
        """Ajoute des styles personnalisés au document"""
        styles = doc.styles
        
        # Style pour les titres de section
        try:
            section_style = styles.add_style('Section Title', WD_STYLE_TYPE.PARAGRAPH)
            section_style.font.size = Inches(0.16)  # 12pt
            section_style.font.bold = True
            section_style.font.color.rgb = RGBColor(0, 51, 102)  # Bleu foncé
            section_style.paragraph_format.space_after = Inches(0.1)
            section_style.paragraph_format.keep_with_next = True
        except:
            logging.warning("Style 'Section Title' déjà existant")
        
        # Style pour le contenu des sections
        try:
            content_style = styles.add_style('Section Content', WD_STYLE_TYPE.PARAGRAPH)
            content_style.font.size = Inches(0.14)  # 11pt
            content_style.paragraph_format.left_indent = Inches(0.25)
            content_style.paragraph_format.space_after = Inches(0.15)
        except:
            logging.warning("Style 'Section Content' déjà existant")
        
        # Style pour l'en-tête
        try:
            header_style = styles.add_style('Document Header', WD_STYLE_TYPE.PARAGRAPH)
            header_style.font.size = Inches(0.18)  # 14pt
            header_style.font.bold = True
            header_style.font.color.rgb = RGBColor(0, 0, 0)
            header_style.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
            header_style.paragraph_format.space_after = Inches(0.2)
        except:
            logging.warning("Style 'Document Header' déjà existant")

    def _add_document_header(self, doc: Document, template_match: TemplateMatch, transcription_filename: str):
        """Ajoute l'en-tête du document"""
        # Titre principal
        header = doc.add_paragraph()
        header.style = 'Document Header'
        header.add_run("COMPTE-RENDU MÉDICAL GÉNÉRÉ AUTOMATIQUEMENT")
        
        # Informations du template
        info_paragraph = doc.add_paragraph()
        info_paragraph.add_run("Template utilisé: ").bold = True
        info_paragraph.add_run(os.path.basename(template_match.template_info.filepath))
        
        # Informations médicales
        if template_match.template_info.medecin and template_match.template_info.medecin != "Non identifié":
            medecin_para = doc.add_paragraph()
            medecin_para.add_run("Médecin: ").bold = True
            medecin_para.add_run(template_match.template_info.medecin)
        
        centre = getattr(template_match.template_info, 'centre_medical', 'Non spécifié')
        if centre and centre != "Non spécifié":
            centre_para = doc.add_paragraph()
            centre_para.add_run("Centre médical: ").bold = True
            centre_para.add_run(centre)
        
        # Type de document
        type_para = doc.add_paragraph()
        type_para.add_run("Type de document: ").bold = True
        type_para.add_run(template_match.template_info.type)
        
        # Informations de génération
        generation_para = doc.add_paragraph()
        generation_para.add_run("Date de génération: ").bold = True
        generation_para.add_run(datetime.now().strftime("%d/%m/%Y à %H:%M"))
        
        score_para = doc.add_paragraph()
        score_para.add_run("Score de correspondance: ").bold = True
        score_para.add_run(f"{template_match.overall_score:.3f} ({template_match.confidence_level})")
        
        filling_para = doc.add_paragraph()
        filling_para.add_run("Pourcentage de remplissage: ").bold = True
        filling_para.add_run(f"{template_match.filling_percentage:.1f}%")
        
        # Ligne de séparation
        doc.add_paragraph("_" * 80)

    def _add_filled_sections(self, doc: Document, template_match: TemplateMatch):
        """Ajoute les sections remplies au document"""
        if not template_match.extracted_data:
            logging.warning("❌ Aucune section à remplir trouvée")
            doc.add_paragraph("Aucune section n'a pu être remplie automatiquement.")
            return
        
        logging.info(f"📝 Génération de {len(template_match.extracted_data)} sections remplies")
        
        # Ajouter un titre pour les sections remplies
        sections_title = doc.add_paragraph()
        sections_title.add_run("CONTENU EXTRAIT ET STRUCTURÉ").bold = True
        sections_title.add_run().font.size = Inches(0.18)
        
        for section_name, content in template_match.extracted_data.items():
            # Titre de section
            section_title = doc.add_paragraph()
            section_title.style = 'Section Title'
            section_title.add_run(f"{section_name.upper()}")
            
            # Contenu de section
            section_content = doc.add_paragraph()
            section_content.style = 'Section Content'
            section_content.add_run(content)
            
            logging.info(f"   ✅ Section ajoutée: {section_name} ({len(content)} caractères)")

    def _add_missing_sections(self, doc: Document, template_match: TemplateMatch):
        """Ajoute les sections manquantes au document"""
        missing_sections = [s.section_name for s in template_match.section_matches.values() if not s.can_fill]
        
        if missing_sections:
            logging.info(f"⚠️  {len(missing_sections)} sections manquantes identifiées")
            
            # Titre pour les sections manquantes
            missing_title = doc.add_paragraph()
            missing_title.add_run("SECTIONS NON REMPLIES").bold = True
            missing_title.add_run().font.color.rgb = RGBColor(204, 102, 0)  # Orange
            
            missing_subtitle = doc.add_paragraph()
            missing_subtitle.add_run("(Informations non trouvées dans la transcription)")
            missing_subtitle.add_run().font.color.rgb = RGBColor(102, 102, 102)  # Gris
            
            for section in missing_sections:
                missing_para = doc.add_paragraph()
                missing_para.add_run(f"• {section}")
                missing_para.add_run().font.color.rgb = RGBColor(204, 102, 0)
                
                # Ajouter un espace pour remplissage manuel
                placeholder = doc.add_paragraph()
                placeholder.style = 'Section Content'
                placeholder.add_run("[À COMPLÉTER MANUELLEMENT]")
                placeholder.add_run().font.color.rgb = RGBColor(153, 153, 153)  # Gris clair
                placeholder.add_run().italic = True

    def _add_original_transcription(self, doc: Document, transcription: str):
        """Ajoute la transcription originale en annexe"""
        # Saut de page
        doc.add_page_break()
        
        # Titre de l'annexe
        annexe_title = doc.add_paragraph()
        annexe_title.add_run("ANNEXE - TRANSCRIPTION ORIGINALE").bold = True
        annexe_title.add_run().font.size = Inches(0.16)
        annexe_title.add_run().font.color.rgb = RGBColor(102, 102, 102)
        
        # Ligne de séparation
        doc.add_paragraph("=" * 60)
        
        # Transcription originale
        transcription_para = doc.add_paragraph()
        transcription_para.add_run(transcription)
        transcription_para.add_run().font.size = Inches(0.12)  # Texte plus petit
        transcription_para.add_run().font.color.rgb = RGBColor(51, 51, 51)  # Gris foncé

    def generate_filled_template(self, template_match: TemplateMatch, transcription: str, transcription_filename: str) -> str:
        """
        Génère un template rempli et le sauvegarde au format .doc
        
        Args:
            template_match: Le template avec le meilleur score
            transcription: La transcription originale
            transcription_filename: Le nom du fichier de transcription
            
        Returns:
            str: Le chemin du fichier généré
        """
        logging.info("🚀 Début de la génération du template rempli")
        logging.info(f"📋 Template sélectionné: {template_match.template_id}")
        logging.info(f"📊 Score: {template_match.overall_score:.3f}")
        logging.info(f"🔧 Remplissage: {template_match.filling_percentage:.1f}%")
        
        try:
            # Créer un nouveau document Word
            doc = Document()
            
            # Ajouter les styles personnalisés
            self._add_custom_styles(doc)
            
            # Ajouter l'en-tête du document
            self._add_document_header(doc, template_match, transcription_filename)
            
            # Ajouter les sections remplies
            self._add_filled_sections(doc, template_match)
            
            # Ajouter les sections manquantes
            self._add_missing_sections(doc, template_match)
            
            # Ajouter la transcription originale en annexe
            self._add_original_transcription(doc, transcription)
            
            # Générer le nom de fichier de sortie
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            safe_template_id = template_match.template_id.replace('/', '_').replace('\\', '_')
            output_filename = f"template_rempli_{safe_template_id}_{timestamp}.docx"
            output_path = os.path.join(self.output_dir, output_filename)
            
            # Sauvegarder le document
            doc.save(output_path)
            
            logging.info(f"✅ Template rempli généré avec succès:")
            logging.info(f"   📁 Fichier: {output_path}")
            logging.info(f"   📏 Taille: {os.path.getsize(output_path)} bytes")
            logging.info(f"   📋 Sections remplies: {len(template_match.extracted_data)}")
            logging.info(f"   ⚠️  Sections manquantes: {len([s for s in template_match.section_matches.values() if not s.can_fill])}")
            
            return output_path
            
        except Exception as e:
            logging.error(f"❌ Erreur lors de la génération du template: {e}")
            raise

    def display_generation_summary(self, template_match: TemplateMatch, output_path: str):
        """Affiche un résumé de la génération dans les logs"""
        logging.info("=" * 80)
        logging.info("📊 RÉSUMÉ DE LA GÉNÉRATION")
        logging.info("=" * 80)
        logging.info(f"🎯 Template utilisé: {template_match.template_id}")
        logging.info(f"📁 Template source: {os.path.basename(template_match.template_info.filepath)}")
        logging.info(f"👨‍⚕️ Médecin: {template_match.template_info.medecin}")
        logging.info(f"🏥 Centre: {getattr(template_match.template_info, 'centre_medical', 'Non spécifié')}")
        logging.info(f"📝 Type: {template_match.template_info.type}")
        logging.info(f"📊 Score de correspondance: {template_match.overall_score:.3f} ({template_match.confidence_level})")
        logging.info(f"🔧 Pourcentage de remplissage: {template_match.filling_percentage:.1f}%")
        logging.info(f"📋 Sections remplies: {len(template_match.extracted_data)}")
        logging.info(f"⚠️  Sections manquantes: {len([s for s in template_match.section_matches.values() if not s.can_fill])}")
        logging.info(f"💾 Fichier généré: {os.path.basename(output_path)}")
        logging.info(f"📏 Taille du fichier: {os.path.getsize(output_path)} bytes")
        logging.info("=" * 80)


def main():
    """Fonction principale qui utilise le premier fichier pour matcher puis génère le template"""
    
    # Configuration du logging
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    
    # Chemin de la base de données
    db_path = DB_PATH
    
    # Exemple de transcription
    transcription_filename = "default.73.931915433.rtf_3650535_radiologie.doc"
    transcription_content = """ la Technique :** 3 plans T2, diffusion axiale, T2 grand champ et T1 Dixon.
Résultats 
L'utérus est antéversé, antéfléchi, latéralisé à droite, de taille normale pour l'âge.
L'endomètre est fin, mesurant moins de 2 mm.
Pas d'adénomyose franche.
Aspect normal du col utérin et du vagin.
L'ovaire droit, en position postérieure, mesure 18 x 11 mm avec présence de 4 follicules.
L'ovaire gauche, en position latéro-utérine, présente un volumineux endométriome de 45 mm, typique en hypersignal T1 Dixon.
Deuxième endométriome accolé à l'ovaire droit, périphérique, mesurant 13 mm.
Pas d'épaississement marqué du torus ni des ligaments utéro-sacrés.
Pas d'autre localisation pelvienne.
Pas d'épanchement pelvien.
Pas d'anomalie de la vessie.
Pas d'adénomégalie pelvienne, pas de dilatation des uretères.
en Conclusion 
Endométriome ovarien droit périphérique de 13 mm.
Endométriome ovarien gauche centro-ovarien de 45 mm."""
    
    if not os.path.exists(db_path):
        logging.error(f"❌ Base de données non trouvée: {db_path}")
        return
    
    try:
        logging.info("🚀 DÉMARRAGE DU PROCESSUS COMPLET")
        logging.info("=" * 80)
        
        # ÉTAPE 1: Matching avec le premier fichier
        logging.info("📍 ÉTAPE 1: MATCHING DES TEMPLATES")
        matcher = TemplateMatcher(db_path)
        matches = matcher.match_templates(transcription_content, transcription_filename, k=3)
        
        if not matches:
            logging.error("❌ Aucun template trouvé")
            return
        
        # Sélectionner le meilleur template
        best_match = matches[0]
        logging.info(f"✅ Meilleur template sélectionné: {best_match.template_id}")
        
        # ÉTAPE 2: Génération avec le deuxième fichier  
        logging.info("📍 ÉTAPE 2: GÉNÉRATION DU TEMPLATE REMPLI")
        generator = TemplateGenerator()
        output_path = generator.generate_filled_template(
            best_match, 
            transcription_content, 
            transcription_filename
        )
        
        # ÉTAPE 3: Affichage du résumé
        logging.info("📍 ÉTAPE 3: RÉSUMÉ FINAL")
        generator.display_generation_summary(best_match, output_path)
        
        logging.info("🎉 PROCESSUS TERMINÉ AVEC SUCCÈS")
        
    except Exception as e:
        logging.error(f"❌ Erreur dans le processus principal: {e}")


if __name__ == "__main__":
    main()