From 1713aa30b5a28f2f731131ecdd7ada21b9155ba0 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sat, 30 Aug 2025 23:10:43 +0200 Subject: [PATCH] page corrections ajout de boutons --- CONTEXT_MATCHING_FIX.md | 134 ++++++++++++++++++++++++++++++ analyse_orthographe_grammaire.py | 30 +++---- api_corrections.py | 137 +++++++++++++++++++++++++++++-- dictionnaire_personnalise.txt | 5 +- livre.org | 8 +- rapport_orthographe_grammaire.md | 11 ++- requirements.txt | 16 ++-- 7 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 CONTEXT_MATCHING_FIX.md diff --git a/CONTEXT_MATCHING_FIX.md b/CONTEXT_MATCHING_FIX.md new file mode 100644 index 0000000..cce4333 --- /dev/null +++ b/CONTEXT_MATCHING_FIX.md @@ -0,0 +1,134 @@ +# Correction API - Context Matching Fix + +## Problème résolu + +L'API de corrections (`api_corrections.py`) ne parvenait pas à trouver le contexte des erreurs grammaticales dans les chapitres. Spécifiquement, lorsqu'un utilisateur tentait d'appliquer une correction avec le payload suivant : + +```json +{ + "action": "apply_correction", + "error_type": "grammar", + "chapter": "Chapitre 1", + "error_index": 2, + "correction": "voir :" +} +``` + +L'API retournait une erreur 404 avec le message "Contexte non trouvé dans le chapitre". + +## Cause du problème + +Le problème était dû à deux facteurs principaux : + +1. **Recherche de contexte trop stricte** : L'API utilisait une correspondance exacte pour trouver le contexte de l'erreur dans le chapitre, sans tenir compte des variations possibles dans les espaces, la ponctuation ou le formatage. + +2. **Différences entre le rapport et le fichier source** : Le contexte stocké dans le rapport d'erreurs pouvait différer légèrement du texte réel dans le fichier `livre.org`, notamment en ce qui concerne les espaces et la troncature du texte. + +## Solution implémentée + +### 1. Amélioration de la recherche de contexte + +Nous avons implémenté une approche de recherche de contexte à plusieurs niveaux, du plus strict au plus flexible : + +```python +# 1. Échapper les caractères spéciaux dans le contexte +escaped_context = re.escape(context) +# 2. Remplacer les espaces échappées par un motif qui accepte plusieurs espaces +flexible_context = escaped_context.replace('\\ ', '\\s+') +# 3. Créer un motif qui permet des variations +flexible_pattern = flexible_context + +# Essayer d'abord avec le motif flexible +context_match = re.search(flexible_pattern, chapter_content) +``` + +### 2. Stratégies de fallback + +Si la première approche échoue, nous utilisons des stratégies de fallback de plus en plus flexibles : + +```python +# Si ça ne fonctionne pas, essayer avec une approche encore plus flexible +if not context_match: + # Extraire les mots significatifs du contexte (ignorer les mots très courts) + words = [word for word in re.findall(r'\b\w+\b', context) if len(word) > 3] + if words: + # Créer un motif qui recherche une phrase contenant ces mots dans l'ordre + words_pattern = r'.*'.join([re.escape(word) for word in words]) + context_match = re.search(words_pattern, chapter_content) + +# Si toujours pas de correspondance, essayer de trouver juste l'erreur et son contexte immédiat +if not context_match and error_text: + # Chercher l'erreur avec quelques mots avant et après + error_vicinity = r'[^\n]{0,30}' + re.escape(error_text) + r'[^\n]{0,30}' + context_match = re.search(error_vicinity, chapter_content) +``` + +### 3. Amélioration du remplacement de texte + +Une fois le contexte trouvé, nous avons également amélioré la logique de remplacement pour gérer différents scénarios : + +```python +# Déterminer quelle stratégie de remplacement utiliser +if matched_text == context: + # Si le texte correspond exactement au contexte, utiliser la méthode simple + corrected_context = context.replace(error_text, correction) + modified_chapter = chapter_content.replace(matched_text, corrected_context) +else: + # Si nous avons utilisé une correspondance flexible, nous devons être plus précis + # Trouver la position de l'erreur dans le contexte original + error_pos = context.find(error_text) + if error_pos >= 0: + # Essayer de trouver l'erreur exacte dans le texte trouvé + error_in_match = re.search(re.escape(error_text), matched_text) + if error_in_match: + # Remplacer directement l'erreur dans le texte trouvé + start, end = error_in_match.span() + corrected_matched_text = matched_text[:start] + correction + matched_text[end:] + modified_chapter = chapter_content.replace(matched_text, corrected_matched_text) + else: + # Fallback: remplacer tout le texte trouvé + corrected_context = context.replace(error_text, correction) + modified_chapter = chapter_content.replace(matched_text, corrected_context) +``` + +### 4. Ajout d'informations de débogage + +Nous avons ajouté des logs détaillés pour faciliter le diagnostic des problèmes : + +```python +# Ajouter des logs pour le débogage +print(f"Correction appliquée: '{error_text}' -> '{correction}'") +print(f"Contexte trouvé: '{matched_text}'") +print(f"Contexte après correction: '{corrected_context if 'corrected_context' in locals() else 'N/A'}'") + +# Si le chapitre n'a pas été modifié, c'est qu'il y a un problème +if modified_chapter == chapter_content: + print("AVERTISSEMENT: Le chapitre n'a pas été modifié après la correction!") +``` + +En cas d'échec, l'API retourne également des informations de débogage détaillées : + +```python +return jsonify({ + 'success': False, + 'message': 'Contexte non trouvé dans le chapitre', + 'debug_info': { + 'context': context, + 'error_text': error_text, + 'correction': correction, + 'chapter_excerpt': chapter_content[:200] # Premiers 200 caractères du chapitre + } +}), 404 +``` + +## Résultat + +Avec ces modifications, l'API peut maintenant trouver et corriger correctement les erreurs grammaticales, même lorsque le contexte exact ne correspond pas parfaitement au texte du chapitre. Le test avec le payload spécifique mentionné dans la description du problème fonctionne désormais correctement. + +## Améliorations futures possibles + +1. **Mise en cache des correspondances** : Pour améliorer les performances, on pourrait mettre en cache les correspondances entre les contextes d'erreur et les positions dans le fichier. + +2. **Interface utilisateur pour les erreurs** : Ajouter une interface utilisateur pour afficher les erreurs de correspondance et permettre à l'utilisateur de sélectionner manuellement le texte à corriger. + +3. **Normalisation des rapports** : Modifier le processus de génération des rapports d'erreurs pour stocker plus d'informations sur le contexte exact, comme les positions des erreurs dans le fichier. \ No newline at end of file diff --git a/analyse_orthographe_grammaire.py b/analyse_orthographe_grammaire.py index 2d6cf34..5154dc7 100644 --- a/analyse_orthographe_grammaire.py +++ b/analyse_orthographe_grammaire.py @@ -15,8 +15,8 @@ import re import os import csv import argparse -from spellchecker import SpellChecker import language_tool_python +from spellchecker import SpellChecker # Définir les arguments en ligne de commande parser = argparse.ArgumentParser(description='Analyser les fautes d\'orthographe et de grammaire dans un fichier Org-mode.') @@ -77,7 +77,7 @@ def load_custom_dictionary(file_path): Retourne un ensemble de mots à considérer comme corrects. """ custom_words = set() - + # Vérifier si le fichier existe if os.path.exists(file_path): with open(file_path, 'r', encoding='utf-8') as file: @@ -86,7 +86,7 @@ def load_custom_dictionary(file_path): line = line.strip() if line and not line.startswith('#'): custom_words.add(line.lower()) - + return custom_words def check_spelling(text, lang='fr', custom_dict_path='dictionnaire_personnalise.txt'): @@ -95,34 +95,34 @@ def check_spelling(text, lang='fr', custom_dict_path='dictionnaire_personnalise. Utilise un dictionnaire personnalisé pour exclure certains mots de la vérification. """ spell = SpellChecker(language=lang) - + # Charger le dictionnaire personnalisé custom_words = load_custom_dictionary(custom_dict_path) - + # Ajouter les mots du dictionnaire personnalisé au dictionnaire du vérificateur if custom_words: spell.word_frequency.load_words(custom_words) - + # Diviser le texte en mots words = re.findall(r'\b\w+\b', text.lower()) - + # Trouver les mots mal orthographiés misspelled = spell.unknown(words) - + # Créer un dictionnaire avec les mots mal orthographiés et leurs suggestions spelling_errors = {} for word in misspelled: # Vérifier si le mot est dans le dictionnaire personnalisé if word in custom_words: continue - + # Obtenir les suggestions de correction suggestions = spell.candidates(word) # Limiter à 5 suggestions maximum suggestions_list = list(suggestions) if suggestions is not None else [] suggestions_list = suggestions_list[:5] spelling_errors[word] = suggestions_list - + return spelling_errors def check_grammar(text, lang='fr'): @@ -131,17 +131,17 @@ def check_grammar(text, lang='fr'): """ # Initialiser l'outil de vérification grammaticale tool = language_tool_python.LanguageTool(lang) - + # Vérifier le texte matches = tool.check(text) - + # Créer une liste d'erreurs grammaticales grammar_errors = [] for match in matches: # Ignorer les erreurs d'orthographe (déjà traitées par le vérificateur d'orthographe) if match.ruleId.startswith('MORFOLOGIK_RULE'): continue - + error = { 'message': match.message, 'context': match.context, @@ -151,10 +151,10 @@ def check_grammar(text, lang='fr'): 'rule': match.ruleId } grammar_errors.append(error) - + # Fermer l'outil pour libérer les ressources tool.close() - + return grammar_errors def generate_error_report(chapters, output_path): diff --git a/api_corrections.py b/api_corrections.py index 76a42f6..5122607 100644 --- a/api_corrections.py +++ b/api_corrections.py @@ -6,6 +6,12 @@ Ce script: 1. Fournit des endpoints pour appliquer des corrections au fichier livre.org 2. Permet d'ajouter des mots au dictionnaire personnalisé 3. Permet de marquer des erreurs comme "à ne pas traiter" dans le CSV + +Modifications (2025-08-30): +- Amélioration de la recherche des chapitres dans le fichier livre.org +- Le motif de recherche est maintenant plus flexible pour gérer les chapitres avec des tags ou du texte supplémentaire +- Ajout de logs de débogage pour aider à diagnostiquer les problèmes de correspondance de chapitres +- Amélioration des fonctions load_spelling_errors et load_grammar_errors pour utiliser une correspondance flexible """ import os @@ -59,11 +65,28 @@ def apply_correction(data): content = file.read() # Trouver la section du chapitre - chapter_pattern = r'^\*\* ' + re.escape(chapter) + r'$(.*?)(?=^\*\* |\Z)' + # Motif plus flexible qui permet des textes supplémentaires après le nom du chapitre + chapter_pattern = r'^\*\* ' + re.escape(chapter) + r'(?:\s+.*?)?$(.*?)(?=^\*\* |\Z)' chapter_match = re.search(chapter_pattern, content, re.MULTILINE | re.DOTALL) + # Si le motif flexible ne trouve pas de correspondance, essayer avec le motif original if not chapter_match: - return jsonify({'success': False, 'message': f'Chapitre "{chapter}" non trouvé'}), 404 + # Ajouter des logs pour le débogage + print(f"Recherche du chapitre: '{chapter}'") + print(f"Motif utilisé: {chapter_pattern}") + + # Lister tous les titres de chapitres dans le fichier pour le débogage + all_chapters = re.findall(r'^\*\* (.*?)$', content, re.MULTILINE) + print(f"Chapitres trouvés dans le fichier: {all_chapters}") + + return jsonify({ + 'success': False, + 'message': f'Chapitre "{chapter}" non trouvé', + 'debug_info': { + 'pattern_used': chapter_pattern, + 'chapters_in_file': all_chapters + } + }), 404 chapter_content = chapter_match.group(1) chapter_start = chapter_match.start() @@ -96,17 +119,91 @@ def apply_correction(data): context = error['context'] # Trouver le contexte dans le chapitre - context_pattern = re.escape(context.replace(error_text, error_text)) - context_match = re.search(context_pattern, chapter_content) + # Créer un motif plus flexible pour la recherche de contexte + # 1. Échapper les caractères spéciaux dans le contexte + escaped_context = re.escape(context) + # 2. Remplacer les espaces échappées par un motif qui accepte plusieurs espaces + flexible_context = escaped_context.replace('\\ ', '\\s+') + # 3. Créer un motif qui permet des variations au début et à la fin du contexte + flexible_pattern = flexible_context + + # Essayer d'abord avec le motif flexible + context_match = re.search(flexible_pattern, chapter_content) + + # Si ça ne fonctionne pas, essayer avec une approche encore plus flexible + if not context_match: + # Extraire les mots significatifs du contexte (ignorer les mots très courts) + words = [word for word in re.findall(r'\b\w+\b', context) if len(word) > 3] + if words: + # Créer un motif qui recherche une phrase contenant ces mots dans l'ordre + words_pattern = r'.*'.join([re.escape(word) for word in words]) + context_match = re.search(words_pattern, chapter_content) + + # Si toujours pas de correspondance, essayer de trouver juste l'erreur et son contexte immédiat + if not context_match and error_text: + # Chercher l'erreur avec quelques mots avant et après + error_vicinity = r'[^\n]{0,30}' + re.escape(error_text) + r'[^\n]{0,30}' + context_match = re.search(error_vicinity, chapter_content) if not context_match: - return jsonify({'success': False, 'message': 'Contexte non trouvé dans le chapitre'}), 404 + # Ajouter des logs de débogage + print(f"Contexte non trouvé: '{context}'") + print(f"Motif flexible utilisé: {flexible_pattern}") + print(f"Erreur à corriger: '{error_text}'") + print(f"Correction à appliquer: '{correction}'") + print(f"Extrait du chapitre (100 premiers caractères): '{chapter_content[:100]}'") + + return jsonify({ + 'success': False, + 'message': 'Contexte non trouvé dans le chapitre', + 'debug_info': { + 'context': context, + 'error_text': error_text, + 'correction': correction, + 'chapter_excerpt': chapter_content[:200] # Premiers 200 caractères du chapitre + } + }), 404 - # Remplacer l'erreur dans le contexte - corrected_context = context.replace(error_text, correction) + # Remplacer l'erreur dans le contexte trouvé + matched_text = context_match.group(0) - # Remplacer le contexte dans le chapitre - modified_chapter = chapter_content.replace(context, corrected_context) + # Déterminer quelle stratégie de remplacement utiliser + if matched_text == context: + # Si le texte correspond exactement au contexte, utiliser la méthode simple + corrected_context = context.replace(error_text, correction) + modified_chapter = chapter_content.replace(matched_text, corrected_context) + else: + # Si nous avons utilisé une correspondance flexible, nous devons être plus précis + # Trouver la position de l'erreur dans le contexte original + error_pos = context.find(error_text) + if error_pos >= 0: + # Calculer les positions relatives dans le texte trouvé + error_length = len(error_text) + + # Essayer de trouver l'erreur exacte dans le texte trouvé + error_in_match = re.search(re.escape(error_text), matched_text) + if error_in_match: + # Remplacer directement l'erreur dans le texte trouvé + start, end = error_in_match.span() + corrected_matched_text = matched_text[:start] + correction + matched_text[end:] + modified_chapter = chapter_content.replace(matched_text, corrected_matched_text) + else: + # Fallback: remplacer tout le texte trouvé + corrected_context = context.replace(error_text, correction) + modified_chapter = chapter_content.replace(matched_text, corrected_context) + else: + # Si nous ne pouvons pas localiser l'erreur, utiliser une approche plus simple + corrected_context = context.replace(error_text, correction) + modified_chapter = chapter_content.replace(matched_text, corrected_context) + + # Ajouter des logs pour le débogage + print(f"Correction appliquée: '{error_text}' -> '{correction}'") + print(f"Contexte trouvé: '{matched_text}'") + print(f"Contexte après correction: '{corrected_context if 'corrected_context' in locals() else 'N/A'}'") + + # Si le chapitre n'a pas été modifié, c'est qu'il y a un problème + if modified_chapter == chapter_content: + print("AVERTISSEMENT: Le chapitre n'a pas été modifié après la correction!") # Mettre à jour le contenu complet new_content = content[:chapter_start] + '** ' + chapter + modified_chapter + content[chapter_end:] @@ -214,9 +311,20 @@ def load_spelling_errors(chapter): errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md') + # Recherche exacte d'abord if chapter in errors_by_chapter: return errors_by_chapter[chapter]['spelling'] + # Si pas trouvé, essayer une recherche plus flexible + # Chercher un chapitre qui commence par le même nom (sans les tags ou autres textes) + for chap_name in errors_by_chapter.keys(): + # Vérifier si le chapitre commence par le nom recherché + if chap_name.startswith(chapter) or chapter.startswith(chap_name): + print(f"Correspondance flexible trouvée: '{chapter}' -> '{chap_name}'") + return errors_by_chapter[chap_name]['spelling'] + + print(f"Aucune erreur d'orthographe trouvée pour le chapitre: '{chapter}'") + print(f"Chapitres disponibles: {list(errors_by_chapter.keys())}") return [] def load_grammar_errors(chapter): @@ -230,9 +338,20 @@ def load_grammar_errors(chapter): errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md') + # Recherche exacte d'abord if chapter in errors_by_chapter: return errors_by_chapter[chapter]['grammar'] + # Si pas trouvé, essayer une recherche plus flexible + # Chercher un chapitre qui commence par le même nom (sans les tags ou autres textes) + for chap_name in errors_by_chapter.keys(): + # Vérifier si le chapitre commence par le nom recherché + if chap_name.startswith(chapter) or chapter.startswith(chap_name): + print(f"Correspondance flexible trouvée: '{chapter}' -> '{chap_name}'") + return errors_by_chapter[chap_name]['grammar'] + + print(f"Aucune erreur grammaticale trouvée pour le chapitre: '{chapter}'") + print(f"Chapitres disponibles: {list(errors_by_chapter.keys())}") return [] # Pour tester l'API indépendamment diff --git a/dictionnaire_personnalise.txt b/dictionnaire_personnalise.txt index 21b2c4f..8fbd78e 100644 --- a/dictionnaire_personnalise.txt +++ b/dictionnaire_personnalise.txt @@ -5,4 +5,7 @@ # Noms propres eryndor -# Termes spécifiques \ No newline at end of file +# Termes spécifiques +chuck +bobette +bleh \ No newline at end of file diff --git a/livre.org b/livre.org index cdd4064..034768f 100644 --- a/livre.org +++ b/livre.org @@ -33,9 +33,9 @@ Allez hein zou zou Là non plus pas de titre à afficher ------------- -** Chapitre 1 : title: +** Chapitre 1 -Celui là on doit le voir: chapitre 1 au dessus ici. +Celui là on doit le voir : chapitre 1 au dessus ici. Dans un monde lointain, il y avait une île mystérieuse où les arbres avaient des feuilles qui brillaient comme des étoiles. Un jeune aventurier nommé Eryndor y arriva un jour, attiré par les légendes de l'île. Il découvrit un temple caché où les dieux anciens avaient laissé des secrets et des pouvoirs magiques. @@ -49,7 +49,7 @@ Blah blah Bleh bob trouva un cristal qui lui permit de communiquer avec les esprits de la nature. Avec leur aide, il put vaincre les ténèbres qui menaçaient l'île et restaurer la lumière éternelle. L'île fut sauvée et Eryndor devint un héros légendaire. 1111111111111111 -** Chapitre 2 : title: +** Chapitre 2 :title: 2222222222222 Chuck fait des trucs @@ -59,6 +59,6 @@ Bleh bob trouva un cristal qui lui permit de communiquer avec les esprits de la Oui bon heu #+end_comment -** Chapitre 3 : title: +** Chapitre 3 :title: 33333333333333333 Bobette et bob sont sur un bateau diff --git a/rapport_orthographe_grammaire.md b/rapport_orthographe_grammaire.md index f0cf710..c5d35eb 100644 --- a/rapport_orthographe_grammaire.md +++ b/rapport_orthographe_grammaire.md @@ -4,9 +4,9 @@ ### Erreurs d'orthographe -- **eeeeeeeeeeeeeeeeeeeee**: Aucune suggestion - **eeeeeeeeeeeeeeeeee**: Aucune suggestion -- **zou**: cou, mou, fou, zoo, ou +- **zou**: fou, sou, pou, zoo, cou +- **eeeeeeeeeeeeeeeeeeeee**: Aucune suggestion ### Erreurs grammaticales @@ -41,9 +41,8 @@ Aucune erreur grammaticale détectée. ### Erreurs d'orthographe -- **blah**: flash, ah, boa, lac, blet +- **blah**: blasé, bac, beau, boa, la - **bleh**: bleu, blet -- **eryndor**: Aucune suggestion ### Erreurs grammaticales @@ -101,7 +100,7 @@ Aucune erreur grammaticale détectée. ### Erreurs d'orthographe -- **bobette**: burette, tomette, omette, belette, molette +- **bobette**: brouette, blette, couette, tomette, bette ### Erreurs grammaticales @@ -115,5 +114,5 @@ Aucune erreur grammaticale détectée. ## Résumé - **Nombre total de chapitres analysés**: 5 -- **Nombre total d'erreurs d'orthographe**: 8 +- **Nombre total d'erreurs d'orthographe**: 7 - **Nombre total d'erreurs grammaticales**: 13 diff --git a/requirements.txt b/requirements.txt index 4d27aa5..5662c6d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,7 @@ -flask==3.0.2 -matplotlib>=3.8.0 -pandas>=2.0.0 -numpy>=1.21.0,<2.0.0 -scipy>=1.11.0 -argparse>=1.4.0 -pyspellchecker>=0.7.2 -language-tool-python>=2.7.1 -wordcloud>=1.9.2 \ No newline at end of file +Flask==3.1.2 +language_tool_python==2.9.4 +matplotlib==3.10.6 +numpy==2.3.2 +pandas==2.3.2 +scipy==1.16.1 +wordcloud==1.9.4