book-generator-orgmode/api_corrections.py

361 lines
17 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
API pour traiter les requêtes de correction depuis la page web.
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
2025-08-30 23:10:43 +02:00
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
import re
import json
import csv
from flask import Flask, request, jsonify, Blueprint
# Créer un Blueprint pour l'API
corrections_api = Blueprint('corrections_api', __name__)
# Chemin vers les fichiers
LIVRE_PATH = 'livre.org'
DICT_PATH = 'dictionnaire_personnalise.txt'
CSV_PATH = 'resume_erreurs.csv'
@corrections_api.route('/api/corrections', methods=['POST'])
def handle_corrections():
"""Endpoint pour traiter les requêtes de correction."""
data = request.json
if not data or 'action' not in data:
return jsonify({'success': False, 'message': 'Action non spécifiée'}), 400
action = data['action']
if action == 'apply_correction':
return apply_correction(data)
elif action == 'ignore_error':
return ignore_error(data)
elif action == 'add_to_dictionary':
return add_to_dictionary(data)
else:
return jsonify({'success': False, 'message': 'Action non reconnue'}), 400
def apply_correction(data):
"""Applique une correction au fichier livre.org."""
# Vérifier les paramètres requis
required_params = ['error_type', 'chapter', 'error_index', 'correction']
if not all(param in data for param in required_params):
return jsonify({'success': False, 'message': 'Paramètres manquants'}), 400
error_type = data['error_type']
chapter = data['chapter']
error_index = data['error_index']
correction = data['correction']
try:
# Lire le contenu du fichier
with open(LIVRE_PATH, 'r', encoding='utf-8') as file:
content = file.read()
# Trouver la section du chapitre
2025-08-30 23:10:43 +02:00
# 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)
2025-08-30 23:10:43 +02:00
# Si le motif flexible ne trouve pas de correspondance, essayer avec le motif original
if not chapter_match:
2025-08-30 23:10:43 +02:00
# 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()
chapter_end = chapter_match.end()
# Appliquer la correction en fonction du type d'erreur
if error_type == 'spelling':
# Charger les erreurs d'orthographe pour ce chapitre
errors = load_spelling_errors(chapter)
if error_index >= len(errors):
return jsonify({'success': False, 'message': 'Index d\'erreur invalide'}), 400
error = errors[error_index]
word = error['word']
# Remplacer le mot dans le contenu du chapitre
modified_chapter = re.sub(r'\b' + re.escape(word) + r'\b', correction, chapter_content)
# Mettre à jour le contenu complet
new_content = content[:chapter_start] + '** ' + chapter + modified_chapter + content[chapter_end:]
elif error_type == 'grammar':
# Charger les erreurs grammaticales pour ce chapitre
errors = load_grammar_errors(chapter)
if error_index >= len(errors):
return jsonify({'success': False, 'message': 'Index d\'erreur invalide'}), 400
error = errors[error_index]
error_text = error['error_text']
context = error['context']
# Trouver le contexte dans le chapitre
2025-08-30 23:10:43 +02:00
# 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:
2025-08-30 23:10:43 +02:00
# 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 trouvé
matched_text = context_match.group(0)
2025-08-30 23:10:43 +02:00
# 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'}'")
2025-08-30 23:10:43 +02:00
# 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:]
else:
return jsonify({'success': False, 'message': 'Type d\'erreur non reconnu'}), 400
# Écrire le contenu modifié dans le fichier
with open(LIVRE_PATH, 'w', encoding='utf-8') as file:
file.write(new_content)
return jsonify({'success': True, 'message': 'Correction appliquée avec succès'})
except Exception as e:
return jsonify({'success': False, 'message': f'Erreur lors de l\'application de la correction: {str(e)}'}), 500
def ignore_error(data):
"""Marque une erreur comme 'à ne pas traiter' dans le CSV."""
# Vérifier les paramètres requis
required_params = ['error_type', 'chapter', 'error_index']
if not all(param in data for param in required_params):
return jsonify({'success': False, 'message': 'Paramètres manquants'}), 400
error_type = data['error_type']
chapter = data['chapter']
error_index = data['error_index']
try:
# Lire le CSV des erreurs
rows = []
with open(CSV_PATH, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
headers = next(reader) # Lire les en-têtes
# Ajouter les en-têtes à la liste des lignes
rows.append(headers)
# Parcourir les lignes et mettre à jour celle du chapitre concerné
for row in reader:
if len(row) > 0 and row[0] == chapter:
# Mettre à jour la ligne en fonction du type d'erreur
if error_type == 'spelling' and len(row) > 1:
# Décrémenter le nombre d'erreurs d'orthographe
spelling_errors = int(row[1]) - 1
row[1] = str(max(0, spelling_errors))
# Mettre à jour le total
if len(row) > 3:
row[3] = str(int(row[3]) - 1)
elif error_type == 'grammar' and len(row) > 2:
# Décrémenter le nombre d'erreurs grammaticales
grammar_errors = int(row[2]) - 1
row[2] = str(max(0, grammar_errors))
# Mettre à jour le total
if len(row) > 3:
row[3] = str(int(row[3]) - 1)
rows.append(row)
# Écrire les modifications dans le CSV
with open(CSV_PATH, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(rows)
return jsonify({'success': True, 'message': 'Erreur ignorée avec succès'})
except Exception as e:
return jsonify({'success': False, 'message': f'Erreur lors de l\'ignorance de l\'erreur: {str(e)}'}), 500
def add_to_dictionary(data):
"""Ajoute un mot au dictionnaire personnalisé."""
# Vérifier les paramètres requis
if 'word' not in data:
return jsonify({'success': False, 'message': 'Mot non spécifié'}), 400
word = data['word']
try:
# Vérifier si le mot est déjà dans le dictionnaire
existing_words = set()
if os.path.exists(DICT_PATH):
with open(DICT_PATH, 'r', encoding='utf-8') as file:
for line in file:
line = line.strip()
if line and not line.startswith('#'):
existing_words.add(line.lower())
# Si le mot n'est pas déjà dans le dictionnaire, l'ajouter
if word.lower() not in existing_words:
with open(DICT_PATH, 'a', encoding='utf-8') as file:
file.write(f"\n{word}")
return jsonify({'success': True, 'message': 'Mot ajouté au dictionnaire avec succès'})
except Exception as e:
return jsonify({'success': False, 'message': f'Erreur lors de l\'ajout du mot au dictionnaire: {str(e)}'}), 500
def load_spelling_errors(chapter):
"""Charge les erreurs d'orthographe pour un chapitre donné depuis le rapport."""
# Cette fonction devrait charger les erreurs d'orthographe depuis le rapport
# Pour simplifier, nous utilisons une implémentation fictive
# Dans une implémentation réelle, il faudrait parser le rapport d'erreurs
# Exemple d'implémentation fictive
from generate_corrections_page import parse_error_report
errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md')
2025-08-30 23:10:43 +02:00
# Recherche exacte d'abord
if chapter in errors_by_chapter:
return errors_by_chapter[chapter]['spelling']
2025-08-30 23:10:43 +02:00
# 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):
"""Charge les erreurs grammaticales pour un chapitre donné depuis le rapport."""
# Cette fonction devrait charger les erreurs grammaticales depuis le rapport
# Pour simplifier, nous utilisons une implémentation fictive
# Dans une implémentation réelle, il faudrait parser le rapport d'erreurs
# Exemple d'implémentation fictive
from generate_corrections_page import parse_error_report
errors_by_chapter = parse_error_report('rapport_orthographe_grammaire.md')
2025-08-30 23:10:43 +02:00
# Recherche exacte d'abord
if chapter in errors_by_chapter:
return errors_by_chapter[chapter]['grammar']
2025-08-30 23:10:43 +02:00
# 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
if __name__ == '__main__':
app = Flask(__name__)
app.register_blueprint(corrections_api)
app.run(debug=True, port=5001)