ITSkillsCenter
Intelligence Artificielle

Résumer automatiquement de longs rapports sans perdre les informations clés

7 min de lecture

Les entreprises produisent chaque semaine une quantité impressionnante de documents : rapports d’activité, études de marché, comptes rendus de réunion, audits techniques, bilans financiers, analyses concurrentielles. Ces documents peuvent faire des dizaines ou des centaines de pages. Personne ne les lit en entier. Et pourtant, ils contiennent souvent des informations critiques qui influencent des décisions importantes.

La résumé automatique change la donne. En quelques secondes, un modèle de langage peut extraire l’essentiel d’un document long, identifier les décisions importantes, les chiffres clés et les points d’action, et présenter tout cela dans un format lisible et exploitable.

Mais résumer automatiquement un long rapport sans perdre les informations clés est un défi technique non trivial. Ce tutoriel vous guide à travers une méthode fiable : le résumé hiérarchique avec validation des passages critiques.

Comprendre les défis du résumé de documents longs

La résumé automatique souffre d’un problème fondamental : les modèles de langage ont une fenêtre de contexte limitée. Si votre rapport fait 50 pages, vous ne pouvez pas le fournir entièrement à GPT-4 ou à un modèle local en une seule fois.

Le problème de la fenêtre de contexte

La plupart des modèles acceptent entre 8 000 et 128 000 tokens. Un rapport de 100 pages fait facilement 150 000 mots, soit 200 000 tokens. Même les modèles avec de grandes fenêtres ont tendance à « oublier » les informations du début quand le document est très long.

Le problème de la perte d’information

Un résumé naïf génère souvent un texte fluide mais superficiel. Les détails importants, les chiffres précis, les noms propres et les décisions actées sont souvent les premiers à disparaître. C’est acceptable pour un article de blog, pas pour un rapport d’audit ou une étude de marché.

La solution : le résumé hiérarchique

L’approche la plus fiable consiste à :

  1. Découper le rapport en sections
  2. Résumer chaque section indépendamment
  3. Extraire les points critiques (chiffres, décisions, actions)
  4. Consolider les résumés en un résumé final
  5. Vérifier la cohérence et la complétude

💡 Conseil : ne résumez jamais directement un document de plus de 20 pages en un seul prompt. Le modèle produit un résumé « lisse » qui semble complet mais qui omet systématiquement les détails techniques importants.

Préparer l’environnement et extraire le texte

Installation des dépendances

pip install pymupdf requests tiktoken

Extraction du texte par sections

import fitz  # PyMuPDF
import re

def extract_sections_from_pdf(pdf_path: str) -> list[dict]:
    """Extrait le texte du PDF organisé par sections/chapitres."""
    doc = fitz.open(pdf_path)
    sections = []
    current_section = {"title": "Introduction", "content": "", "pages": []}
    
    for page_num, page in enumerate(doc):
        text = page.get_text("text", sort=True)
        
        # Détecter les titres de section (heuristique simple)
        lines = text.split('\n')
        for line in lines:
            line = line.strip()
            if not line:
                continue
            
            # Heuristique : ligne courte en majuscules = titre potentiel
            if (len(line) < 80 and 
                (line.isupper() or line.startswith(('CHAPITRE', 'SECTION', '1.', '2.', '3.', 'PARTIE')))):
                if current_section["content"].strip():
                    sections.append(current_section.copy())
                current_section = {"title": line, "content": "", "pages": [page_num + 1]}
            else:
                current_section["content"] += line + " "
                if page_num + 1 not in current_section["pages"]:
                    current_section["pages"].append(page_num + 1)
    
    if current_section["content"].strip():
        sections.append(current_section)
    
    return sections

# Utilisation
sections = extract_sections_from_pdf("rapport_annuel.pdf")
for s in sections:
    print(f"Section: {s['title']} | {len(s['content'])} chars | Pages: {s['pages']}")

Résumer chaque section indépendamment

Module de résumé par section

import requests

def summarize_section(section: dict, model="gemma3") -> dict:
    """Résume une section en préservant les informations clés."""
    
    prompt = f"""Tu es un expert en analyse de documents professionnels.

Résume la section suivante en respectant ces règles STRICTES :
1. Conserve TOUS les chiffres, pourcentages et statistiques mentionnés
2. Liste les décisions et recommandations de manière explicite
3. Identifie les actions à entreprendre et leurs responsables si mentionnés
4. Mentionne tous les noms propres importants (personnes, entreprises, projets)
5. Le résumé doit faire entre 150 et 300 mots maximum
6. Utilise des puces pour les points importants

Section : {section['title']}

Contenu :
{section['content'][:4000]}  # Limiter à ~1000 tokens par section

Résumé structuré :"""

    response = requests.post(
        "http://localhost:11434/api/chat",
        json={
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "stream": False
        },
        timeout=120
    )
    
    summary = response.json()["message"]["content"]
    
    return {
        "title": section["title"],
        "pages": section["pages"],
        "summary": summary,
        "original_length": len(section["content"])
    }

# Résumer toutes les sections
summaries = []
for i, section in enumerate(sections):
    print(f"Résumé section {i+1}/{len(sections)}: {section['title'][:50]}")
    summary = summarize_section(section)
    summaries.append(summary)

print(f"✅ {len(summaries)} sections résumées")

Gérer les sections très longues avec découpage récursif

def chunk_text(text: str, max_chars: int = 4000, overlap: int = 200) -> list[str]:
    """Découpe un texte long en morceaux avec chevauchement."""
    chunks = []
    start = 0
    while start < len(text):
        end = start + max_chars
        chunk = text[start:end]
        # Couper à la fin d'une phrase si possible
        last_period = chunk.rfind('.')
        if last_period > max_chars * 0.7:
            chunk = chunk[:last_period + 1]
            end = start + last_period + 1
        chunks.append(chunk.strip())
        start = end - overlap
    return chunks

def summarize_long_section(section: dict, model="gemma3") -> dict:
    """Résume une section très longue avec découpage récursif."""
    content = section["content"]
    
    if len(content) <= 4000:
        return summarize_section(section)
    
    # Découper en chunks
    chunks = chunk_text(content, max_chars=3500, overlap=150)
    
    # Résumer chaque chunk
    chunk_summaries = []
    for chunk in chunks:
        temp_section = {**section, "content": chunk}
        result = summarize_section(temp_section)
        chunk_summaries.append(result["summary"])
    
    # Consolider les résumés des chunks
    combined = "\n\n".join(chunk_summaries)
    consolidation_section = {**section, "content": combined}
    final = summarize_section(consolidation_section)
    
    return {**final, "chunks_processed": len(chunks)}

💡 Conseil : pour les sections très techniques (tableaux financiers, données chiffrées denses), utilisez un prompt spécialisé qui demande explicitement d'extraire tous les chiffres sous forme structurée avant de résumer. Cela évite la perte des données quantitatives.

Consolider en un résumé exécutif final

Générer le résumé exécutif

def generate_executive_summary(summaries: list[dict], model="gemma3") -> str:
    """Génère un résumé exécutif complet à partir des résumés de sections."""
    
    # Construire le contexte consolidé
    context_parts = []
    for s in summaries:
        pages_str = f"Pages {s['pages'][0]}-{s['pages'][-1]}" if len(s['pages']) > 1 else f"Page {s['pages'][0]}"
        context_parts.append(f"## {s['title']} ({pages_str})\n{s['summary']}")
    
    consolidated = "\n\n".join(context_parts)
    
    prompt = f"""Tu es un analyste senior chargé de rédiger un résumé exécutif professionnel.

À partir des résumés de sections ci-dessous, rédige un résumé exécutif complet comprenant :

1. **Synthèse générale** (3-5 phrases, vision d'ensemble)
2. **Points clés et chiffres importants** (liste structurée)
3. **Décisions et recommandations principales** (liste numérotée)
4. **Actions à entreprendre** (tableau : quoi | qui | quand si mentionné)
5. **Points de vigilance** (risques ou points critiques)

Le résumé exécutif doit faire entre 400 et 600 mots.
Garde un ton professionnel et factuel.
N'ajoute aucune information non présente dans les résumés fournis.

Résumés des sections :
{consolidated}

Résumé exécutif :"""

    response = requests.post(
        "http://localhost:11434/api/chat",
        json={
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "stream": False
        },
        timeout=120
    )
    
    return response.json()["message"]["content"]

executive_summary = generate_executive_summary(summaries)
print(executive_summary)

Extraire et valider les informations critiques

Un résumé fluide peut cacher des omissions importantes. La validation est une étape clé.

Extraction automatique des informations critiques

def extract_critical_info(full_text: str, model="gemma3") -> dict:
    """Extrait les informations critiques qui ne doivent pas être perdues."""
    
    prompt = f"""Analyse ce texte et extrais UNIQUEMENT les informations suivantes, sous forme structurée :

- Tous les chiffres et statistiques (avec leur contexte)
- Toutes les dates et échéances mentionnées
- Tous les noms de personnes et leurs rôles
- Toutes les décisions formelles prises
- Tous les montants financiers
- Toutes les actions assignées avec responsable

Réponds en JSON strict :
{{
  "chiffres": ["stat1", "stat2"],
  "dates": ["date1", "date2"],
  "personnes": ["nom1 - rôle", "nom2 - rôle"],
  "decisions": ["decision1", "decision2"],
  "montants": ["montant1 - contexte"],
  "actions": ["action1 - responsable", "action2 - responsable"]
}}

Texte à analyser :
{full_text[:8000]}"""

    response = requests.post(
        "http://localhost:11434/api/chat",
        json={
            "model": model,
            "messages": [{"role": "user", "content": prompt}],
            "stream": False
        },
        timeout=120
    )
    
    import json, re
    text = response.json()["message"]["content"]
    # Extraire le JSON
    json_match = re.search(r'\{.*\}', text, re.DOTALL)
    if json_match:
        try:
            return json.loads(json_match.group())
        except:
            pass
    return {"raw": text}

# Valider que le résumé contient les infos critiques
critical_info = extract_critical_info(full_text)
print("Informations critiques extraites:", critical_info)

Vérification de cohérence

def validate_summary_completeness(summary: str, critical_info: dict) -> dict:
    """Vérifie que le résumé mentionne les informations critiques."""
    issues = []
    
    for chiffre in critical_info.get("chiffres", []):
        # Extraire juste le nombre
        numbers = re.findall(r'\d+[.,]?\d*%?', chiffre)
        for num in numbers[:1]:  # Vérifier le premier nombre de chaque stat
            if num not in summary:
                issues.append(f"Chiffre manquant dans le résumé : {chiffre}")
    
    for decision in critical_info.get("decisions", []):
        # Vérifier qu'un mot-clé de la décision est dans le résumé
        keywords = [w for w in decision.split() if len(w) > 5][:3]
        if keywords and not any(kw.lower() in summary.lower() for kw in keywords):
            issues.append(f"Décision potentiellement absente : {decision[:80]}")
    
    return {
        "is_complete": len(issues) == 0,
        "missing_items": issues,
        "completeness_score": max(0, 1 - len(issues) / max(1, len(critical_info.get("chiffres", [])) + len(critical_info.get("decisions", []))))
    }

validation = validate_summary_completeness(executive_summary, critical_info)
print(f"Complétude: {validation['completeness_score']:.0%}")
if validation['missing_items']:
    print("Points manquants:", validation['missing_items'])

Automatiser et créer des rapports formatés

Script complet d'automatisation

def summarize_report(pdf_path: str, output_path: str = None) -> dict:
    """Pipeline complet : PDF → résumé exécutif validé."""
    print(f"📄 Traitement de : {pdf_path}")
    
    # 1. Extraction
    sections = extract_sections_from_pdf(pdf_path)
    full_text = " ".join([s["content"] for s in sections])
    print(f"   {len(sections)} sections extraites")
    
    # 2. Résumé par sections
    summaries = []
    for i, section in enumerate(sections):
        summary = summarize_long_section(section)
        summaries.append(summary)
        print(f"   ✓ Section {i+1}/{len(sections)} résumée")
    
    # 3. Résumé exécutif
    executive = generate_executive_summary(summaries)
    
    # 4. Extraction des infos critiques
    critical = extract_critical_info(full_text)
    
    # 5. Validation
    validation = validate_summary_completeness(executive, critical)
    
    result = {
        "document": pdf_path,
        "sections_count": len(sections),
        "executive_summary": executive,
        "section_summaries": summaries,
        "critical_info": critical,
        "validation": validation
    }
    
    # 6. Export
    if output_path:
        import json
        with open(output_path, 'w', encoding='utf-8') as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
        print(f"   ✅ Rapport sauvegardé : {output_path}")
    
    return result

# Utilisation
result = summarize_report("rapport_annuel_2025.pdf", "resume_rapport_annuel.json")
print("\n=== RÉSUMÉ EXÉCUTIF ===")
print(result["executive_summary"])

💡 Conseil : planifiez une étape de relecture humaine pour tous les résumés de documents à fort impact (audits, rapports financiers, études réglementaires). La validation automatique détecte les omissions évidentes, mais un œil humain reste indispensable pour juger de la pertinence et de la nuance.

Conclusion

Résumer automatiquement de longs rapports sans perdre les informations clés est un défi solvable avec la bonne architecture. Le résumé hiérarchique — sections → consolidation → résumé exécutif — combiné à l'extraction et à la validation des informations critiques, donne des résultats fiables et professionnels.

Cette approche est adaptée à tous les contextes : rapports d'audit, études de marché, bilans financiers, documentations techniques ou comptes rendus de réunion. Elle peut fonctionner entièrement en local avec Ollama, garantissant la confidentialité des données sensibles.

La clé du succès reste la validation : un résumé IA non contrôlé peut être fluide mais lacunaire. Un processus de validation, même léger, transforme un outil expérimental en solution professionnelle fiable.

Vous voulez maîtriser l'automatisation des processus documentaires avec l'IA ? Retrouvez nos tutoriels complets sur ITSkillsCenter.io pour développer des compétences concrètes et directement applicables.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 350.000 FCFA
Parlons de Votre Projet
Publicité

Articles Similaires