361 lines
No EOL
17 KiB
Python
361 lines
No EOL
17 KiB
Python
#!/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
|
|
|
|
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
|
|
# 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:
|
|
# 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
|
|
# 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:
|
|
# 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)
|
|
|
|
# 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:]
|
|
|
|
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')
|
|
|
|
# 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):
|
|
"""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')
|
|
|
|
# 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
|
|
if __name__ == '__main__':
|
|
app = Flask(__name__)
|
|
app.register_blueprint(corrections_api)
|
|
app.run(debug=True, port=5001) |