page corrections ajout de boutons

This commit is contained in:
Tykayn 2025-08-30 23:10:43 +02:00 committed by tykayn
parent 7095f9633b
commit 1713aa30b5
7 changed files with 297 additions and 44 deletions

134
CONTEXT_MATCHING_FIX.md Normal file
View file

@ -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.

View file

@ -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):

View file

@ -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

View file

@ -5,4 +5,7 @@
# Noms propres
eryndor
# Termes spécifiques
# Termes spécifiques
chuck
bobette
bleh

View file

@ -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

View file

@ -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

View file

@ -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
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