File size: 6,172 Bytes
8a995db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Ce fichier poc/utils/md_parser.py

import os
import re
import yaml
import logging
from langchain_core.documents import Document

# Récupération du logger configuré centralement
logger = logging.getLogger(__name__)

def parse_md_to_documents(corpus_dir: str) -> list[Document]:
    """
    Parcourt le dossier du corpus, lit chaque fichier Markdown (.md),
    extrait le Front Matter YAML (métadonnées globales) et découpe le corps
    du texte en chunks sémantiques basés sur les Titres 2 (##).
    
    Chaque action ou bloc de hors-périmètre devient un unique Document LangChain.
    
    :param corpus_dir: Chemin vers le dossier contenant les fichiers .md
    :return: Liste d'objets Document utilisables directement par FAISS
    """
    documents = []
    
    if not os.path.exists(corpus_dir):
        logger.error(f"Le dossier du corpus est introuvable : {corpus_dir}")
        return documents

    # 1. Parcourir tous les fichiers du dossier corpus
    for filename in os.listdir(corpus_dir):
        if not filename.endswith('.md'):
            continue  # On ignore les fichiers qui ne sont pas du Markdown
            
        file_path = os.path.join(corpus_dir, filename)
        logger.info(f"Début du traitement du fichier : {filename}")
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
                
            # 2. Extraction du Front Matter YAML (Métadonnées globales)
            # Les métadonnées sont délimitées par des triples tirets --- en haut du fichier
            front_matter = {}
            body_text = content
            
            if content.startswith('---'):
                # Expression régulière pour isoler le bloc YAML et le reste du texte
                match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)', content, re.DOTALL)
                if match:
                    yaml_content = match.group(1)
                    body_text = match.group(2)
                    # Chargement sécurisé du dictionnaire de métadonnées YAML
                    front_matter = yaml.safe_load(yaml_content) or {}
                    logger.debug(f"Front Matter extrait avec succès pour {filename}")

            # 3. Découpage du texte par Action (Titre 2 : ##)
            # On utilise un regex avec capture pour conserver les titres '## Nom de l'action'
            # re.split va générer une liste alternant le texte précédent (souvent vide), le titre, puis le contenu
            sections = re.split(r'^(##\s+.*)$', body_text, flags=re.MULTILINE)
            
            # La première section contient le texte avant le premier ## (Ex: Titre 1 du Process)
            # On peut l'extraire pour enrichir le contexte global si nécessaire
            process_intro = sections[0].strip()
            
            # 4. Itération par paires (Titre H2, Contenu du H2)
            # On commence à l'index 1 et on avance de 2 en 2
            for i in range(1, len(sections), 2):
                h2_title = sections[i].strip()
                h2_content = sections[i+1].strip() if (i+1) < len(sections) else ""
                
                # Nettoyage cosmétique du titre de l'action (on enlève les '## ')
                action_name = h2_title.lstrip('#').strip()
                
                # Reconstitution du texte complet du chunk (Titre + Corps)
                # On préserve scrupuleusement le formatage Markdown d'origine (comme le **Gras**)
                full_chunk_text = f"{h2_title}\n\n{h2_content}"
                
                # 5. Extraction des métadonnées locales spécifiques à l'action
                # On cherche les lignes clés "Écran de départ : ..." et "Mots-clés : ..." dans le texte du bloc
                ecran_depart_match = re.search(r'(?:Écran de départ|Ecran de depart)\s*:\s*(.*)', h2_content, re.IGNORECASE)
                mots_cles_match = re.search(r'(?:Mots-clés|Mots-cles|Mots clés)\s*:\s*(.*)', h2_content, re.IGNORECASE)
                
                ecran_depart = ecran_depart_match.group(1).strip() if ecran_depart_match else "N/A"
                mots_cles = mots_cles_match.group(1).strip() if mots_cles_match else "N/A"
                
                # 6. Fusion des métadonnées (Globales + Locales)
                # C'est l'équivalent de votre mapping standardisé pour format_docs()
                metadata = {
                    "source_file": filename,
                    "process": front_matter.get("titre", filename.replace(".md", "")),
                    "auteur": front_matter.get("auteur", "Inconnu"),
                    "version": front_matter.get("version", "1.0"),
                    "date_maj": front_matter.get("date_maj", "N/A"),
                    "action": action_name,
                    "ecran_depart": ecran_depart,
                    "mots_cles": mots_cles
                }
                
                # 7. Instanciation du Document LangChain
                doc = Document(
                    page_content=full_chunk_text,
                    metadata=metadata
                )
                documents.append(doc)
                
            logger.info(f"Fichier {filename} traité : {len(sections) // 2} chunks créés.")
            
        except Exception as e:
            logger.error(f"Erreur lors du parsing du fichier {filename} : {e}")
            continue

    logger.info(f"Fin du parsing global. Total de documents LangChain créés : {len(documents)}")
    return documents

# Bloc de test unitaire rapide pour valider le comportement en isolation
if __name__ == "__main__":
    # Configuration d'un logging basique pour le test
    logging.basicConfig(level=logging.INFO)
    
    # Simulation d'un appel vers un dossier de test local
    test_dir = "./corpus"
    print(f"--- Test du parser sur le dossier {test_dir} ---")
    docs = parse_md_to_documents(test_dir)
    
    if docs:
        print(f"\nExemple de structure du premier Document LangChain généré :")
        print(f"METADATA : {docs[0].metadata}")
        print(f"CONTENT PREVIEW (200 chars) :\n{docs[0].page_content[:200]}...")