visualisation des corrections et api flask pour modifier le livre
This commit is contained in:
parent
0680c7594e
commit
7095f9633b
14 changed files with 1593 additions and 276 deletions
34
README.md
34
README.md
|
@ -77,8 +77,34 @@ Analyse les fautes d'orthographe et de grammaire dans votre livre et génère:
|
|||
- Un rapport détaillé au format Markdown des erreurs trouvées dans chaque chapitre
|
||||
- Un fichier CSV résumant le nombre d'erreurs par chapitre
|
||||
- Des suggestions de correction pour chaque erreur identifiée
|
||||
- Utilise un dictionnaire personnalisé pour exclure certains mots de la vérification
|
||||
|
||||
Cet outil vous aide à améliorer la qualité linguistique de votre texte en identifiant les problèmes potentiels chapitre par chapitre.
|
||||
#### Interface de correction interactive
|
||||
`python generate_corrections_page.py`
|
||||
Génère une page web interactive dans le dossier build qui permet de:
|
||||
- Visualiser les erreurs orthographiques et grammaticales dans leur contexte
|
||||
- Appliquer directement les corrections suggérées au fichier livre.org
|
||||
- Ignorer certaines erreurs en les marquant comme "à ne pas traiter"
|
||||
- Ajouter des mots au dictionnaire personnalisé
|
||||
|
||||
Pour utiliser l'interface de correction:
|
||||
1. Exécutez `python generate_corrections_page.py` pour générer la page
|
||||
2. Lancez le serveur Flask avec `python app.py`
|
||||
3. Accédez à http://localhost:5000/build/corrections.html dans votre navigateur
|
||||
|
||||
Cet outil vous aide à améliorer la qualité linguistique de votre texte en identifiant les problèmes potentiels chapitre par chapitre et en facilitant leur correction.
|
||||
|
||||
### Analyse des enchaînements d'intrigue
|
||||
`python analyse_intrigues.py`
|
||||
Analyse les enchaînements d'intrigue dans votre livre et génère:
|
||||
- Un graphique avec une ligne unique composée de segments colorés représentant chaque intrigue
|
||||
- Un diagramme de Gantt coloré des intrigues
|
||||
- Une page web interactive dans le dossier build qui présente:
|
||||
- Un graphique interactif des intrigues avec informations au survol
|
||||
- Une liste hiérarchique des intrigues et sous-intrigues
|
||||
- Les visualisations PNG et SVG générées
|
||||
|
||||
Cet outil vous aide à visualiser comment vos intrigues s'enchaînent et s'imbriquent tout au long de votre livre. Les visualisations sont également intégrées au tableau de bord web existant.
|
||||
|
||||
### Statistiques
|
||||
`bash up_infos.sh`
|
||||
|
@ -134,7 +160,11 @@ Un tableau de bord web interactif est disponible pour visualiser les données de
|
|||
- Graphique de progression de l'écriture
|
||||
- Graphique de réseau des personnages (déplaçable à la souris)
|
||||
- Graphique de réseau reliant les personnages aux intrigues (déplaçable à la souris, les labels des personnages sont toujours visibles, les labels des intrigues ne sont visibles que lors du survol)
|
||||
- Chronologie des intrigues
|
||||
- Chronologie des intrigues avec:
|
||||
- Visualisation interactive des intrigues
|
||||
- Enchaînement des intrigues avec segments colorés
|
||||
- Diagramme de Gantt coloré des intrigues
|
||||
- Lien vers une page d'analyse détaillée des intrigues
|
||||
|
||||
Pour générer le tableau de bord, exécutez:
|
||||
```bash
|
||||
|
|
470
analyse_intrigues.py
Normal file
470
analyse_intrigues.py
Normal file
|
@ -0,0 +1,470 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Script d'analyse des enchaînements d'intrigue dans le livre.
|
||||
Ce script:
|
||||
1. Analyse le CSV des intrigues
|
||||
2. Attribue une couleur à chaque ligne d'intrigue selon un gradient rouge-vert (rouge pour le début, vert pour la fin)
|
||||
3. Réalise un graphique avec une seule ligne faite de segments colorés
|
||||
4. Sauvegarde le graphique en PNG et SVG
|
||||
5. Crée une page web interactive dans le dossier build
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.colors as mcolors
|
||||
import numpy as np
|
||||
import os
|
||||
import json
|
||||
from matplotlib.colors import to_hex
|
||||
import shutil
|
||||
|
||||
# Créer le dossier build s'il n'existe pas
|
||||
if not os.path.exists('build'):
|
||||
os.makedirs('build')
|
||||
|
||||
# Fonction pour générer un gradient de couleurs du rouge au vert
|
||||
def generate_red_to_green_gradient(n):
|
||||
"""Génère n couleurs dans un gradient allant du rouge au vert"""
|
||||
# Utiliser un gradient de rouge (début) à vert (fin)
|
||||
colors = []
|
||||
for i in range(n):
|
||||
# Calculer la position dans le gradient (0 = rouge, 1 = vert)
|
||||
t = i / max(1, n - 1)
|
||||
|
||||
# Rouge: diminue de 1 à 0
|
||||
r = 1.0 - t
|
||||
# Vert: augmente de 0 à 1
|
||||
g = t
|
||||
# Bleu: toujours à 0
|
||||
b = 0.0
|
||||
|
||||
colors.append(np.array([r, g, b, 1.0]))
|
||||
|
||||
return colors
|
||||
|
||||
# Lire le fichier CSV des intrigues
|
||||
df = pd.read_csv('intrigues.csv')
|
||||
|
||||
# Trier les intrigues par début
|
||||
df = df.sort_values(by=['Début', 'Fin'])
|
||||
|
||||
# Identifier les intrigues principales et les sous-intrigues
|
||||
# Une sous-intrigue est contenue dans une autre intrigue si son début et sa fin
|
||||
# sont compris dans l'intervalle de l'intrigue parente
|
||||
def identify_subplots(df):
|
||||
"""Identifie les relations parent-enfant entre les intrigues"""
|
||||
# Initialiser une colonne pour le parent
|
||||
df['Parent'] = None
|
||||
|
||||
# Pour chaque intrigue, trouver son parent (s'il existe)
|
||||
for i, row in df.iterrows():
|
||||
# Chercher parmi toutes les autres intrigues
|
||||
for j, potential_parent in df.iterrows():
|
||||
if i != j: # Ne pas comparer avec soi-même
|
||||
# Si l'intrigue est contenue dans une autre
|
||||
if (row['Début'] >= potential_parent['Début'] and
|
||||
row['Fin'] <= potential_parent['Fin'] and
|
||||
# S'assurer que ce n'est pas exactement le même intervalle
|
||||
not (row['Début'] == potential_parent['Début'] and
|
||||
row['Fin'] == potential_parent['Fin'])):
|
||||
# Si on a déjà trouvé un parent, prendre le plus spécifique
|
||||
if df.at[i, 'Parent'] is not None:
|
||||
current_parent_idx = df.index[df['Intrigue'] == df.at[i, 'Parent']].tolist()[0]
|
||||
current_parent = df.iloc[current_parent_idx]
|
||||
# Le parent le plus spécifique est celui avec l'intervalle le plus court
|
||||
if ((potential_parent['Fin'] - potential_parent['Début']) <
|
||||
(current_parent['Fin'] - current_parent['Début'])):
|
||||
df.at[i, 'Parent'] = potential_parent['Intrigue']
|
||||
else:
|
||||
df.at[i, 'Parent'] = potential_parent['Intrigue']
|
||||
|
||||
return df
|
||||
|
||||
# Appliquer la fonction pour identifier les sous-intrigues
|
||||
df = identify_subplots(df)
|
||||
|
||||
# Organiser les intrigues en groupes basés sur les relations parent-enfant
|
||||
def organize_plots_by_hierarchy(df):
|
||||
# Créer un dictionnaire pour stocker les groupes d'intrigues
|
||||
plot_groups = {}
|
||||
group_id = 0
|
||||
|
||||
# D'abord, identifier toutes les intrigues principales (sans parent)
|
||||
main_plots = df[df['Parent'].isna()]['Intrigue'].tolist()
|
||||
|
||||
# Pour chaque intrigue principale, créer un groupe avec ses sous-intrigues
|
||||
for main_plot in main_plots:
|
||||
group = [main_plot]
|
||||
# Trouver toutes les sous-intrigues directes
|
||||
subplots = df[df['Parent'] == main_plot]['Intrigue'].tolist()
|
||||
group.extend(subplots)
|
||||
|
||||
# Trouver les sous-intrigues de niveau 2 (sous-intrigues des sous-intrigues)
|
||||
for subplot in subplots:
|
||||
sub_subplots = df[df['Parent'] == subplot]['Intrigue'].tolist()
|
||||
group.extend(sub_subplots)
|
||||
|
||||
plot_groups[group_id] = group
|
||||
group_id += 1
|
||||
|
||||
# Créer un dictionnaire qui associe chaque intrigue à son groupe
|
||||
plot_to_group = {}
|
||||
for group_id, plots in plot_groups.items():
|
||||
for plot in plots:
|
||||
plot_to_group[plot] = group_id
|
||||
|
||||
return plot_groups, plot_to_group
|
||||
|
||||
# Déterminer les valeurs min et max pour les chapitres
|
||||
min_chapter = df['Début'].min()
|
||||
max_chapter = df['Fin'].max()
|
||||
chapter_range = max_chapter - min_chapter
|
||||
|
||||
# Créer un dictionnaire de couleurs pour chaque intrigue
|
||||
color_dict = {}
|
||||
|
||||
# Trier les intrigues par position dans l'histoire (début du chapitre)
|
||||
sorted_intrigues = df.sort_values(by='Début')
|
||||
|
||||
# Générer un gradient de couleurs du rouge au vert
|
||||
gradient_colors = generate_red_to_green_gradient(len(df))
|
||||
|
||||
# Assigner les couleurs aux intrigues en fonction de leur position dans l'histoire
|
||||
for i, (_, row) in enumerate(sorted_intrigues.iterrows()):
|
||||
intrigue = row['Intrigue']
|
||||
# Utiliser la position normalisée dans l'histoire pour déterminer la couleur
|
||||
position = (row['Début'] - min_chapter) / chapter_range if chapter_range > 0 else 0
|
||||
# Assigner la couleur du gradient correspondant à la position
|
||||
color_index = min(int(position * (len(gradient_colors) - 1)), len(gradient_colors) - 1)
|
||||
color_dict[intrigue] = to_hex(gradient_colors[i])
|
||||
|
||||
# Créer un graphique avec une ligne unique composée de segments colorés
|
||||
plt.figure(figsize=(12, 4))
|
||||
|
||||
# Tracer chaque segment d'intrigue
|
||||
for i, row in df.iterrows():
|
||||
plt.plot([row['Début'], row['Fin']], [1, 1], linewidth=10,
|
||||
color=color_dict[row['Intrigue']],
|
||||
solid_capstyle='butt',
|
||||
label=row['Intrigue'])
|
||||
|
||||
# Ajouter des marqueurs pour les points de début et fin
|
||||
for i, row in df.iterrows():
|
||||
plt.plot(row['Début'], 1, 'o', color='black', markersize=5)
|
||||
plt.plot(row['Fin'], 1, 'o', color='black', markersize=5)
|
||||
|
||||
# Configurer les axes
|
||||
plt.yticks([]) # Cacher l'axe y
|
||||
plt.xlabel('Chapitres')
|
||||
plt.title('Enchaînement des intrigues')
|
||||
|
||||
# Ajouter une légende unique pour chaque intrigue
|
||||
handles, labels = plt.gca().get_legend_handles_labels()
|
||||
by_label = dict(zip(labels, handles))
|
||||
plt.legend(by_label.values(), by_label.keys(), loc='upper center',
|
||||
bbox_to_anchor=(0.5, -0.15), ncol=3)
|
||||
|
||||
# Ajuster les marges
|
||||
plt.tight_layout()
|
||||
|
||||
# Sauvegarder en PNG et SVG
|
||||
plt.savefig('build/enchaînement_intrigues.png', dpi=300, bbox_inches='tight')
|
||||
plt.savefig('build/enchaînement_intrigues.svg', bbox_inches='tight')
|
||||
|
||||
# Créer un diagramme de Gantt (comme dans gantt_parser.py mais avec des couleurs)
|
||||
fig, ax = plt.subplots(figsize=(12, 8))
|
||||
|
||||
# Tracer chaque intrigue avec sa couleur
|
||||
for i, row in df.iterrows():
|
||||
ax.plot([row['Début'], row['Fin']], [i, i], '-',
|
||||
color=color_dict[row['Intrigue']],
|
||||
linewidth=10)
|
||||
ax.plot([row['Début'], row['Début']], [i-0.1, i+0.1], 'ko')
|
||||
ax.plot([row['Fin'], row['Fin']], [i-0.1, i+0.1], 'ko')
|
||||
|
||||
# Configurer les axes
|
||||
ax.set_yticks(range(len(df)))
|
||||
ax.set_yticklabels(df['Intrigue'])
|
||||
ax.set_xticks(range(int(df['Début'].min()), int(df['Fin'].max())+1))
|
||||
ax.set_xticklabels(ax.get_xticks())
|
||||
ax.set_xlabel('Chapitres')
|
||||
ax.set_title('Diagramme de Gantt des intrigues')
|
||||
|
||||
# Ajuster les marges
|
||||
plt.tight_layout()
|
||||
|
||||
# Sauvegarder en PNG et SVG
|
||||
plt.savefig('build/gantt_intrigues.png', dpi=300, bbox_inches='tight')
|
||||
plt.savefig('build/gantt_intrigues.svg', bbox_inches='tight')
|
||||
|
||||
# Préparer les données pour la page web interactive
|
||||
web_data = []
|
||||
for i, row in df.iterrows():
|
||||
web_data.append({
|
||||
'intrigue': row['Intrigue'],
|
||||
'debut': int(row['Début']),
|
||||
'fin': int(row['Fin']),
|
||||
'color': color_dict[row['Intrigue']],
|
||||
'parent': row['Parent']
|
||||
})
|
||||
|
||||
# Sauvegarder les données en JSON
|
||||
with open('build/intrigues_data.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(web_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# Créer la page HTML interactive pour les intrigues
|
||||
html_content = """<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analyse des Intrigues</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1, h2 {
|
||||
color: #333;
|
||||
}
|
||||
.plot-timeline {
|
||||
height: 100px;
|
||||
position: relative;
|
||||
margin: 30px 0;
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.plot-segment {
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
top: 35px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.plot-segment:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,0.2);
|
||||
}
|
||||
.timeline-marker {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
height: 10px;
|
||||
background-color: #333;
|
||||
}
|
||||
.timeline-label {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
}
|
||||
.plot-list {
|
||||
margin-top: 50px;
|
||||
}
|
||||
.plot-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.subplots {
|
||||
margin-left: 30px;
|
||||
}
|
||||
.plot-info {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
border-left: 5px solid #333;
|
||||
}
|
||||
.images {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Analyse des Intrigues</h1>
|
||||
|
||||
<h2>Enchaînement des intrigues</h2>
|
||||
<div id="plot-timeline" class="plot-timeline"></div>
|
||||
<div id="plot-info" class="plot-info"></div>
|
||||
|
||||
<h2>Liste des intrigues</h2>
|
||||
<div id="plot-list" class="plot-list"></div>
|
||||
|
||||
<div class="images">
|
||||
<h2>Visualisations</h2>
|
||||
<div>
|
||||
<h3>Enchaînement des intrigues</h3>
|
||||
<img src="enchaînement_intrigues.png" alt="Enchaînement des intrigues">
|
||||
</div>
|
||||
<div>
|
||||
<h3>Diagramme de Gantt des intrigues</h3>
|
||||
<img src="gantt_intrigues.png" alt="Diagramme de Gantt des intrigues">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Charger les données
|
||||
fetch('intrigues_data.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Trouver les valeurs min et max pour l'échelle
|
||||
const minChapter = Math.min(...data.map(item => item.debut));
|
||||
const maxChapter = Math.max(...data.map(item => item.fin));
|
||||
const timelineWidth = document.getElementById('plot-timeline').offsetWidth;
|
||||
|
||||
// Fonction pour convertir un numéro de chapitre en position X
|
||||
function chapterToX(chapter) {
|
||||
return ((chapter - minChapter) / (maxChapter - minChapter)) * timelineWidth;
|
||||
}
|
||||
|
||||
// Ajouter les marqueurs de chapitre
|
||||
const timeline = document.getElementById('plot-timeline');
|
||||
for (let i = minChapter; i <= maxChapter; i++) {
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'timeline-marker';
|
||||
marker.style.left = chapterToX(i) + 'px';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'timeline-label';
|
||||
label.textContent = i;
|
||||
label.style.left = chapterToX(i) + 'px';
|
||||
|
||||
timeline.appendChild(marker);
|
||||
timeline.appendChild(label);
|
||||
}
|
||||
|
||||
// Ajouter les segments d'intrigue
|
||||
data.forEach(plot => {
|
||||
const segment = document.createElement('div');
|
||||
segment.className = 'plot-segment';
|
||||
segment.style.left = chapterToX(plot.debut) + 'px';
|
||||
segment.style.width = (chapterToX(plot.fin) - chapterToX(plot.debut)) + 'px';
|
||||
segment.style.backgroundColor = plot.color;
|
||||
segment.setAttribute('data-intrigue', plot.intrigue);
|
||||
|
||||
// Ajouter un événement au survol
|
||||
segment.addEventListener('mouseover', () => {
|
||||
const plotInfo = document.getElementById('plot-info');
|
||||
plotInfo.style.display = 'block';
|
||||
plotInfo.style.borderLeftColor = plot.color;
|
||||
plotInfo.innerHTML = `
|
||||
<h3>${plot.intrigue}</h3>
|
||||
<p>Chapitres ${plot.debut} à ${plot.fin}</p>
|
||||
${plot.parent ? `<p>Fait partie de: ${plot.parent}</p>` : ''}
|
||||
`;
|
||||
});
|
||||
|
||||
segment.addEventListener('mouseout', () => {
|
||||
document.getElementById('plot-info').style.display = 'none';
|
||||
});
|
||||
|
||||
timeline.appendChild(segment);
|
||||
});
|
||||
|
||||
// Organiser les intrigues en hiérarchie
|
||||
const plotsWithoutParent = data.filter(plot => !plot.parent);
|
||||
const plotList = document.getElementById('plot-list');
|
||||
|
||||
// Fonction récursive pour afficher les intrigues et leurs sous-intrigues
|
||||
function displayPlots(plots, container, level = 0) {
|
||||
plots.forEach(plot => {
|
||||
const plotItem = document.createElement('div');
|
||||
plotItem.className = 'plot-item';
|
||||
plotItem.style.backgroundColor = plot.color + '33'; // Ajouter transparence
|
||||
plotItem.style.borderLeft = `5px solid ${plot.color}`;
|
||||
|
||||
plotItem.innerHTML = `
|
||||
<h3>${plot.intrigue}</h3>
|
||||
<p>Chapitres ${plot.debut} à ${plot.fin}</p>
|
||||
`;
|
||||
|
||||
container.appendChild(plotItem);
|
||||
|
||||
// Trouver les sous-intrigues
|
||||
const subplots = data.filter(subPlot => subPlot.parent === plot.intrigue);
|
||||
|
||||
if (subplots.length > 0) {
|
||||
const subplotsContainer = document.createElement('div');
|
||||
subplotsContainer.className = 'subplots';
|
||||
plotItem.appendChild(subplotsContainer);
|
||||
|
||||
displayPlots(subplots, subplotsContainer, level + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Afficher les intrigues principales et leurs sous-intrigues
|
||||
displayPlots(plotsWithoutParent, plotList);
|
||||
})
|
||||
.catch(error => console.error('Erreur lors du chargement des données:', error));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Écrire le fichier HTML pour les intrigues
|
||||
with open('build/intrigues.html', 'w', encoding='utf-8') as f:
|
||||
f.write(html_content)
|
||||
|
||||
# Supprimer l'ancien index.html s'il existe
|
||||
if os.path.exists('build/index.html'):
|
||||
os.remove('build/index.html')
|
||||
|
||||
# Mettre à jour le dashboard.html pour inclure un lien vers la page des intrigues
|
||||
# et ajouter les visualisations d'intrigues
|
||||
if os.path.exists('build/dashboard.html'):
|
||||
with open('build/dashboard.html', 'r', encoding='utf-8') as f:
|
||||
dashboard_content = f.read()
|
||||
|
||||
# Ajouter un lien vers la page des intrigues dans la section des intrigues
|
||||
if '<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>' in dashboard_content:
|
||||
dashboard_content = dashboard_content.replace(
|
||||
'<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>',
|
||||
'<div class="dashboard-section">\n <h2>Chronologie des Intrigues</h2>\n <p><a href="intrigues.html">Voir l\'analyse détaillée des intrigues</a></p>'
|
||||
)
|
||||
|
||||
# Ajouter les nouvelles visualisations à la section des intrigues
|
||||
if '<div class="chart-container" id="plot-timeline"></div>' in dashboard_content:
|
||||
dashboard_content = dashboard_content.replace(
|
||||
'<div class="chart-container" id="plot-timeline"></div>',
|
||||
'<div class="chart-container" id="plot-timeline"></div>\n <h3>Enchaînement des intrigues</h3>\n <div class="chart-container">\n <img src="enchaînement_intrigues.png" alt="Enchaînement des intrigues" style="max-width:100%;">\n </div>\n <h3>Diagramme de Gantt des intrigues</h3>\n <div class="chart-container">\n <img src="gantt_intrigues.png" alt="Diagramme de Gantt des intrigues" style="max-width:100%;">\n </div>'
|
||||
)
|
||||
|
||||
# Écrire le fichier dashboard mis à jour
|
||||
with open('build/dashboard.html', 'w', encoding='utf-8') as f:
|
||||
f.write(dashboard_content)
|
||||
|
||||
print("Analyse des intrigues terminée.")
|
||||
print("Fichiers générés dans le dossier 'build':")
|
||||
print("- enchaînement_intrigues.png")
|
||||
print("- enchaînement_intrigues.svg")
|
||||
print("- gantt_intrigues.png")
|
||||
print("- gantt_intrigues.svg")
|
||||
print("- intrigues_data.json")
|
||||
print("- intrigues.html")
|
|
@ -15,7 +15,7 @@ import re
|
|||
import os
|
||||
import csv
|
||||
import argparse
|
||||
from pyspellchecker import SpellChecker
|
||||
from spellchecker import SpellChecker
|
||||
import language_tool_python
|
||||
|
||||
# Définir les arguments en ligne de commande
|
||||
|
@ -71,12 +71,38 @@ def clean_chapter_content(content):
|
|||
|
||||
return content.strip()
|
||||
|
||||
def check_spelling(text, lang='fr'):
|
||||
def load_custom_dictionary(file_path):
|
||||
"""
|
||||
Charge le dictionnaire personnalisé à partir d'un fichier texte.
|
||||
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:
|
||||
for line in file:
|
||||
# Ignorer les lignes vides et les commentaires
|
||||
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'):
|
||||
"""
|
||||
Vérifie l'orthographe d'un texte et retourne les mots mal orthographiés.
|
||||
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())
|
||||
|
||||
|
@ -86,10 +112,15 @@ def check_spelling(text, lang='fr'):
|
|||
# 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)[:5]
|
||||
suggestions_list = list(suggestions) if suggestions is not None else []
|
||||
suggestions_list = suggestions_list[:5]
|
||||
spelling_errors[word] = suggestions_list
|
||||
|
||||
return spelling_errors
|
||||
|
|
242
api_corrections.py
Normal file
242
api_corrections.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
#!/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
|
||||
"""
|
||||
|
||||
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
|
||||
chapter_pattern = r'^\*\* ' + re.escape(chapter) + r'$(.*?)(?=^\*\* |\Z)'
|
||||
chapter_match = re.search(chapter_pattern, content, re.MULTILINE | re.DOTALL)
|
||||
|
||||
if not chapter_match:
|
||||
return jsonify({'success': False, 'message': f'Chapitre "{chapter}" non trouvé'}), 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
|
||||
context_pattern = re.escape(context.replace(error_text, error_text))
|
||||
context_match = re.search(context_pattern, chapter_content)
|
||||
|
||||
if not context_match:
|
||||
return jsonify({'success': False, 'message': 'Contexte non trouvé dans le chapitre'}), 404
|
||||
|
||||
# Remplacer l'erreur dans le contexte
|
||||
corrected_context = context.replace(error_text, correction)
|
||||
|
||||
# Remplacer le contexte dans le chapitre
|
||||
modified_chapter = chapter_content.replace(context, corrected_context)
|
||||
|
||||
# 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')
|
||||
|
||||
if chapter in errors_by_chapter:
|
||||
return errors_by_chapter[chapter]['spelling']
|
||||
|
||||
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')
|
||||
|
||||
if chapter in errors_by_chapter:
|
||||
return errors_by_chapter[chapter]['grammar']
|
||||
|
||||
return []
|
||||
|
||||
# Pour tester l'API indépendamment
|
||||
if __name__ == '__main__':
|
||||
app = Flask(__name__)
|
||||
app.register_blueprint(corrections_api)
|
||||
app.run(debug=True, port=5001)
|
19
app.py
19
app.py
|
@ -1,11 +1,21 @@
|
|||
from flask import Flask, render_template, request, jsonify
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Application Flask pour le générateur de livre.
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, request, jsonify, send_from_directory
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import re
|
||||
from api_corrections import corrections_api
|
||||
|
||||
app = Flask(__name__, static_folder='static')
|
||||
|
||||
# Enregistrer le blueprint de l'API de corrections
|
||||
app.register_blueprint(corrections_api)
|
||||
|
||||
def load_org_file(filename):
|
||||
if os.path.exists(filename):
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
|
@ -355,5 +365,10 @@ def get_characters_file():
|
|||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/build/<path:path>')
|
||||
def serve_build(path):
|
||||
"""Sert les fichiers statiques du dossier build."""
|
||||
return send_from_directory('build', path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
@ -84,5 +84,9 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"2025-08-30": {
|
||||
"words": 0,
|
||||
"last_update": "2025-08-30 22:34:19"
|
||||
}
|
||||
}
|
8
dictionnaire_personnalise.txt
Normal file
8
dictionnaire_personnalise.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Dictionnaire personnalisé
|
||||
# Ce fichier contient les mots à considérer comme corrects lors de l'analyse orthographique
|
||||
# Un mot par ligne
|
||||
|
||||
# Noms propres
|
||||
eryndor
|
||||
|
||||
# Termes spécifiques
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script pour réparer les fichiers CSV contenant des dates corrompues avec des caractères nuls.
|
||||
Ce script lit un fichier CSV, nettoie les dates corrompues, et écrit un nouveau fichier CSV corrigé.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
def fix_corrupted_csv(input_file, output_file=None, delimiter=';'):
|
||||
"""
|
||||
Répare un fichier CSV contenant des dates corrompues avec des caractères nuls.
|
||||
|
||||
Args:
|
||||
input_file: Chemin vers le fichier CSV à réparer
|
||||
output_file: Chemin vers le fichier CSV de sortie (si None, ajoute '_fixed' au nom du fichier d'entrée)
|
||||
delimiter: Délimiteur utilisé dans le fichier CSV
|
||||
|
||||
Returns:
|
||||
Le chemin vers le fichier CSV réparé
|
||||
"""
|
||||
if output_file is None:
|
||||
base, ext = os.path.splitext(input_file)
|
||||
output_file = f"{base}_fixed{ext}"
|
||||
|
||||
fixed_rows = []
|
||||
corrupted_count = 0
|
||||
total_count = 0
|
||||
|
||||
# Lire le fichier CSV d'entrée
|
||||
with open(input_file, 'r') as csvfile:
|
||||
reader = csv.reader(csvfile, delimiter=delimiter)
|
||||
for row in reader:
|
||||
total_count += 1
|
||||
if not row:
|
||||
continue
|
||||
|
||||
try:
|
||||
# Vérifier si la date est corrompue
|
||||
date_str = row[0]
|
||||
try:
|
||||
# Essayer de parser la date directement
|
||||
datetime.fromisoformat(date_str)
|
||||
except ValueError:
|
||||
# Si la date est corrompue, essayer de l'extraire avec regex
|
||||
match = re.search(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?', date_str)
|
||||
if match:
|
||||
# Remplacer la date corrompue par la date extraite
|
||||
row[0] = match.group(0)
|
||||
corrupted_count += 1
|
||||
print(f"Date corrompue réparée: {date_str} -> {row[0]}")
|
||||
else:
|
||||
print(f"AVERTISSEMENT: Impossible de réparer la date: {date_str}")
|
||||
|
||||
fixed_rows.append(row)
|
||||
except Exception as e:
|
||||
print(f"ERREUR lors du traitement de la ligne: {e}")
|
||||
|
||||
# Écrire le fichier CSV réparé
|
||||
with open(output_file, 'w', newline='') as csvfile:
|
||||
writer = csv.writer(csvfile, delimiter=delimiter)
|
||||
writer.writerows(fixed_rows)
|
||||
|
||||
print(f"\nRésumé:")
|
||||
print(f"Total des lignes traitées: {total_count}")
|
||||
print(f"Lignes corrompues réparées: {corrupted_count}")
|
||||
print(f"Fichier réparé sauvegardé sous: {output_file}")
|
||||
|
||||
return output_file
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python fix_corrupted_csv.py <input_csv_file> [output_csv_file] [delimiter]")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = sys.argv[1]
|
||||
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
delimiter = sys.argv[3] if len(sys.argv) > 3 else ';'
|
||||
|
||||
if not os.path.exists(input_file):
|
||||
print(f"ERREUR: Le fichier {input_file} n'existe pas.")
|
||||
sys.exit(1)
|
||||
|
||||
fixed_file = fix_corrupted_csv(input_file, output_file, delimiter)
|
||||
print(f"\nPour utiliser le fichier réparé avec follow_progress.py, exécutez:")
|
||||
print(f"python follow_progress.py {fixed_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,48 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script pour ajouter un commentaire en début de fichier CSV afin d'éviter
|
||||
qu'il soit exécuté directement comme un script Python.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def fix_csv_file(csv_file_path):
|
||||
"""
|
||||
Ajoute un commentaire en début de fichier CSV pour éviter l'exécution directe.
|
||||
"""
|
||||
if not os.path.exists(csv_file_path):
|
||||
print(f"Erreur: Le fichier {csv_file_path} n'existe pas.")
|
||||
return False
|
||||
|
||||
# Lire le contenu actuel du fichier
|
||||
with open(csv_file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Vérifier si le fichier commence déjà par un commentaire
|
||||
if content.startswith('#'):
|
||||
print(f"Le fichier {csv_file_path} est déjà protégé.")
|
||||
return True
|
||||
|
||||
# Ajouter le commentaire au début du fichier
|
||||
with open(csv_file_path, 'w') as f:
|
||||
f.write('# Ce fichier est un CSV et ne doit pas être exécuté directement avec Python.\n')
|
||||
f.write(content)
|
||||
|
||||
print(f"Le fichier {csv_file_path} a été protégé contre l'exécution directe.")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale.
|
||||
"""
|
||||
# Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv'
|
||||
if len(sys.argv) > 1:
|
||||
csv_file = sys.argv[1]
|
||||
else:
|
||||
csv_file = 'suivi_livre.csv'
|
||||
|
||||
fix_csv_file(csv_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,76 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script pour ajouter un en-tête Python au début d'un fichier CSV afin qu'il
|
||||
puisse être exécuté directement sans erreur et affiche un message d'aide.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def fix_csv_file(csv_file_path):
|
||||
"""
|
||||
Ajoute un en-tête Python au début du fichier CSV pour gérer l'exécution directe.
|
||||
"""
|
||||
if not os.path.exists(csv_file_path):
|
||||
print(f"Erreur: Le fichier {csv_file_path} n'existe pas.")
|
||||
return False
|
||||
|
||||
# Lire le contenu actuel du fichier
|
||||
with open(csv_file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Vérifier si le fichier commence déjà par le script Python
|
||||
if content.startswith('#!/usr/bin/env python3'):
|
||||
print(f"Le fichier {csv_file_path} est déjà protégé.")
|
||||
return True
|
||||
|
||||
# En-tête Python à ajouter au début du fichier
|
||||
python_header = '''#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Ce fichier est un CSV contenant des données de suivi de livre.
|
||||
Il n'est pas destiné à être exécuté directement comme un script Python.
|
||||
|
||||
Pour analyser ces données, utilisez plutôt:
|
||||
- follow_progress.py: pour mettre à jour et analyser les statistiques
|
||||
- generate_dashboard.py: pour générer un tableau de bord visuel
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
def main():
|
||||
print("Ce fichier est un CSV contenant des données de suivi de livre.")
|
||||
print("Il n'est pas destiné à être exécuté directement comme un script Python.")
|
||||
print("\\nPour analyser ces données, utilisez plutôt:")
|
||||
print("- follow_progress.py: pour mettre à jour et analyser les statistiques")
|
||||
print("- generate_dashboard.py: pour générer un tableau de bord visuel")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
# Les données CSV commencent ci-dessous:
|
||||
'''
|
||||
|
||||
# Ajouter l'en-tête Python au début du fichier
|
||||
with open(csv_file_path, 'w') as f:
|
||||
f.write(python_header)
|
||||
f.write(content)
|
||||
|
||||
print(f"Le fichier {csv_file_path} a été protégé contre l'exécution directe.")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale.
|
||||
"""
|
||||
# Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv'
|
||||
if len(sys.argv) > 1:
|
||||
csv_file = sys.argv[1]
|
||||
else:
|
||||
csv_file = 'suivi_livre.csv'
|
||||
|
||||
fix_csv_file(csv_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,52 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script pour restaurer le fichier CSV à un format simple avec juste un commentaire en tête.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
def fix_csv_file(csv_file_path):
|
||||
"""
|
||||
Restaure le fichier CSV à un format simple avec juste un commentaire en tête.
|
||||
"""
|
||||
if not os.path.exists(csv_file_path):
|
||||
print(f"Erreur: Le fichier {csv_file_path} n'existe pas.")
|
||||
return False
|
||||
|
||||
# Lire le contenu actuel du fichier
|
||||
with open(csv_file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extraire les lignes de données (ignorer les lignes de commentaire et le script Python)
|
||||
lines = content.split('\n')
|
||||
data_lines = []
|
||||
for line in lines:
|
||||
# Conserver uniquement les lignes qui contiennent des données CSV (celles avec des points-virgules)
|
||||
if ';' in line and not line.startswith('#'):
|
||||
data_lines.append(line)
|
||||
|
||||
# Réécrire le fichier avec juste un commentaire simple en tête
|
||||
with open(csv_file_path, 'w') as f:
|
||||
f.write('# Ce fichier est un CSV et ne doit pas être exécuté directement avec Python.\n')
|
||||
f.write('# Utilisez view_suivi_livre.py pour visualiser ces données.\n')
|
||||
for line in data_lines:
|
||||
f.write(line + '\n')
|
||||
|
||||
print(f"Le fichier {csv_file_path} a été restauré à un format simple.")
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""
|
||||
Fonction principale.
|
||||
"""
|
||||
# Utiliser le fichier spécifié en argument ou par défaut 'suivi_livre.csv'
|
||||
if len(sys.argv) > 1:
|
||||
csv_file = sys.argv[1]
|
||||
else:
|
||||
csv_file = 'suivi_livre.csv'
|
||||
|
||||
fix_csv_file(csv_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
661
generate_corrections_page.py
Normal file
661
generate_corrections_page.py
Normal file
|
@ -0,0 +1,661 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script pour générer une page web interactive de correction des erreurs orthographiques et grammaticales.
|
||||
Ce script:
|
||||
1. Lit le rapport d'erreurs et le fichier CSV des erreurs
|
||||
2. Génère une page HTML interactive dans le dossier build
|
||||
3. Permet de corriger les erreurs directement dans le fichier livre.org
|
||||
4. Permet d'ajouter des mots au dictionnaire personnalisé
|
||||
5. Permet de marquer des erreurs comme "à ne pas traiter"
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import csv
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
# Créer le dossier build s'il n'existe pas
|
||||
if not os.path.exists('build'):
|
||||
os.makedirs('build')
|
||||
|
||||
# Créer le dossier pour les ressources statiques s'il n'existe pas
|
||||
STATIC_DIR = os.path.join('build', 'static')
|
||||
if not os.path.exists(STATIC_DIR):
|
||||
os.makedirs(STATIC_DIR)
|
||||
|
||||
# Créer les sous-dossiers pour CSS et JS
|
||||
CSS_DIR = os.path.join(STATIC_DIR, 'css')
|
||||
JS_DIR = os.path.join(STATIC_DIR, 'js')
|
||||
|
||||
for directory in [CSS_DIR, JS_DIR]:
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
def parse_error_report(report_path):
|
||||
"""
|
||||
Parse le rapport d'erreurs pour extraire les informations sur les erreurs.
|
||||
"""
|
||||
with open(report_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
|
||||
# Extraire les sections par chapitre
|
||||
chapter_pattern = r'## Chapitre: (.*?)\n\n(.*?)(?=\n---|\Z)'
|
||||
chapters = re.findall(chapter_pattern, content, re.DOTALL)
|
||||
|
||||
errors_by_chapter = {}
|
||||
|
||||
for chapter_title, chapter_content in chapters:
|
||||
# Extraire les erreurs d'orthographe
|
||||
spelling_pattern = r'### Erreurs d\'orthographe\n\n(.*?)(?=\n\n### Erreurs grammaticales|\Z)'
|
||||
spelling_match = re.search(spelling_pattern, chapter_content, re.DOTALL)
|
||||
|
||||
spelling_errors = []
|
||||
if spelling_match and "Aucune erreur d'orthographe détectée" not in spelling_match.group(1):
|
||||
spelling_text = spelling_match.group(1)
|
||||
error_pattern = r'- \*\*(.*?)\*\*: (.*?)(?=\n- \*\*|\Z)'
|
||||
for error_match in re.finditer(error_pattern, spelling_text, re.DOTALL):
|
||||
word = error_match.group(1)
|
||||
suggestions_text = error_match.group(2).strip()
|
||||
suggestions = []
|
||||
if suggestions_text != "Aucune suggestion":
|
||||
suggestions = [s.strip() for s in suggestions_text.split(',')]
|
||||
|
||||
spelling_errors.append({
|
||||
'word': word,
|
||||
'suggestions': suggestions,
|
||||
'type': 'spelling'
|
||||
})
|
||||
|
||||
# Extraire les erreurs grammaticales
|
||||
grammar_pattern = r'### Erreurs grammaticales\n\n(.*?)(?=\n\n---|\Z)'
|
||||
grammar_match = re.search(grammar_pattern, chapter_content, re.DOTALL)
|
||||
|
||||
grammar_errors = []
|
||||
if grammar_match and "Aucune erreur grammaticale détectée" not in grammar_match.group(1):
|
||||
grammar_text = grammar_match.group(1)
|
||||
error_pattern = r'- \*\*Erreur\*\*: (.*?)\n - \*\*Contexte\*\*: (.*?)\n - \*\*Suggestions\*\*: (.*?)(?=\n\n- \*\*Erreur|\Z)'
|
||||
for error_match in re.finditer(error_pattern, grammar_text, re.DOTALL):
|
||||
message = error_match.group(1).strip()
|
||||
context = error_match.group(2).strip()
|
||||
suggestions_text = error_match.group(3).strip()
|
||||
|
||||
# Extraire le texte en gras du contexte (l'erreur elle-même)
|
||||
error_text_match = re.search(r'\*\*(.*?)\*\*', context)
|
||||
error_text = error_text_match.group(1) if error_text_match else ""
|
||||
|
||||
# Nettoyer le contexte pour l'affichage
|
||||
clean_context = re.sub(r'\*\*(.*?)\*\*', r'\1', context)
|
||||
|
||||
suggestions = []
|
||||
if suggestions_text != "Aucune suggestion":
|
||||
suggestions = [s.strip() for s in suggestions_text.split(',')]
|
||||
|
||||
grammar_errors.append({
|
||||
'message': message,
|
||||
'context': clean_context,
|
||||
'error_text': error_text,
|
||||
'suggestions': suggestions,
|
||||
'type': 'grammar'
|
||||
})
|
||||
|
||||
errors_by_chapter[chapter_title] = {
|
||||
'spelling': spelling_errors,
|
||||
'grammar': grammar_errors
|
||||
}
|
||||
|
||||
return errors_by_chapter
|
||||
|
||||
def create_css():
|
||||
"""Crée le fichier CSS pour la page de corrections."""
|
||||
css_content = """
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chapter-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.error-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.error-card.spelling {
|
||||
border-left: 5px solid #3498db;
|
||||
}
|
||||
|
||||
.error-card.grammar {
|
||||
border-left: 5px solid #e74c3c;
|
||||
}
|
||||
|
||||
.error-context {
|
||||
background-color: #f9f9f9;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.error-word {
|
||||
font-weight: bold;
|
||||
color: #e74c3c;
|
||||
text-decoration: underline;
|
||||
text-decoration-style: wavy;
|
||||
text-decoration-color: #e74c3c;
|
||||
}
|
||||
|
||||
.suggestion-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.suggestion-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.suggestion-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background-color: #2ecc71;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 15px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.action-btn.ignore {
|
||||
background-color: #95a5a6;
|
||||
}
|
||||
|
||||
.action-btn.dictionary {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
display: none;
|
||||
background-color: #2ecc71;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: none;
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
background-color: #ddd;
|
||||
cursor: pointer;
|
||||
border-radius: 5px 5px 0 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background-color: white;
|
||||
border-bottom: 2px solid #3498db;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
"""
|
||||
|
||||
with open(os.path.join(CSS_DIR, 'corrections.css'), 'w', encoding='utf-8') as f:
|
||||
f.write(css_content)
|
||||
|
||||
def create_js(errors_by_chapter):
|
||||
"""Crée le fichier JavaScript pour la page de corrections."""
|
||||
# Convertir les données en JSON pour les utiliser dans le JavaScript
|
||||
errors_json = json.dumps(errors_by_chapter, ensure_ascii=False)
|
||||
|
||||
js_content = f"""
|
||||
// Données des erreurs
|
||||
const errorsByChapter = {errors_json};
|
||||
|
||||
// Fonction pour appliquer une correction
|
||||
function applyCorrection(errorType, chapterTitle, errorIndex, correction) {{
|
||||
// Préparer les données à envoyer
|
||||
const data = {{
|
||||
action: 'apply_correction',
|
||||
error_type: errorType,
|
||||
chapter: chapterTitle,
|
||||
error_index: errorIndex,
|
||||
correction: correction
|
||||
}};
|
||||
|
||||
// Envoyer la requête au serveur
|
||||
fetch('/api/corrections', {{
|
||||
method: 'POST',
|
||||
headers: {{
|
||||
'Content-Type': 'application/json',
|
||||
}},
|
||||
body: JSON.stringify(data)
|
||||
}})
|
||||
.then(response => response.json())
|
||||
.then(data => {{
|
||||
if (data.success) {{
|
||||
// Afficher un message de succès
|
||||
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
||||
const successMsg = card.querySelector('.success-message');
|
||||
successMsg.textContent = 'Correction appliquée avec succès!';
|
||||
successMsg.style.display = 'block';
|
||||
|
||||
// Masquer le message après 3 secondes
|
||||
setTimeout(() => {{
|
||||
successMsg.style.display = 'none';
|
||||
}}, 3000);
|
||||
|
||||
// Désactiver les boutons de suggestion
|
||||
const suggestionBtns = card.querySelectorAll('.suggestion-btn');
|
||||
suggestionBtns.forEach(btn => {{
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.5';
|
||||
}});
|
||||
}} else {{
|
||||
// Afficher un message d'erreur
|
||||
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
||||
const errorMsg = card.querySelector('.error-message');
|
||||
errorMsg.textContent = data.message || 'Erreur lors de l\\'application de la correction.';
|
||||
errorMsg.style.display = 'block';
|
||||
|
||||
// Masquer le message après 3 secondes
|
||||
setTimeout(() => {{
|
||||
errorMsg.style.display = 'none';
|
||||
}}, 3000);
|
||||
}}
|
||||
}})
|
||||
.catch(error => {{
|
||||
console.error('Erreur:', error);
|
||||
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
||||
}});
|
||||
}}
|
||||
|
||||
// Fonction pour ignorer une erreur
|
||||
function ignoreError(errorType, chapterTitle, errorIndex) {{
|
||||
// Préparer les données à envoyer
|
||||
const data = {{
|
||||
action: 'ignore_error',
|
||||
error_type: errorType,
|
||||
chapter: chapterTitle,
|
||||
error_index: errorIndex
|
||||
}};
|
||||
|
||||
// Envoyer la requête au serveur
|
||||
fetch('/api/corrections', {{
|
||||
method: 'POST',
|
||||
headers: {{
|
||||
'Content-Type': 'application/json',
|
||||
}},
|
||||
body: JSON.stringify(data)
|
||||
}})
|
||||
.then(response => response.json())
|
||||
.then(data => {{
|
||||
if (data.success) {{
|
||||
// Afficher un message de succès
|
||||
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
||||
const successMsg = card.querySelector('.success-message');
|
||||
successMsg.textContent = 'Erreur ignorée avec succès!';
|
||||
successMsg.style.display = 'block';
|
||||
|
||||
// Masquer le message après 3 secondes
|
||||
setTimeout(() => {{
|
||||
card.style.display = 'none';
|
||||
}}, 1000);
|
||||
}} else {{
|
||||
// Afficher un message d'erreur
|
||||
const card = document.querySelector(`#${{errorType}}-${{chapterTitle.replace(/\\s+/g, '-')}}-${{errorIndex}}`);
|
||||
const errorMsg = card.querySelector('.error-message');
|
||||
errorMsg.textContent = data.message || 'Erreur lors de l\\'ignorance de l\\'erreur.';
|
||||
errorMsg.style.display = 'block';
|
||||
|
||||
// Masquer le message après 3 secondes
|
||||
setTimeout(() => {{
|
||||
errorMsg.style.display = 'none';
|
||||
}}, 3000);
|
||||
}}
|
||||
}})
|
||||
.catch(error => {{
|
||||
console.error('Erreur:', error);
|
||||
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
||||
}});
|
||||
}}
|
||||
|
||||
// Fonction pour ajouter un mot au dictionnaire personnalisé
|
||||
function addToDictionary(word) {{
|
||||
// Préparer les données à envoyer
|
||||
const data = {{
|
||||
action: 'add_to_dictionary',
|
||||
word: word
|
||||
}};
|
||||
|
||||
// Envoyer la requête au serveur
|
||||
fetch('/api/corrections', {{
|
||||
method: 'POST',
|
||||
headers: {{
|
||||
'Content-Type': 'application/json',
|
||||
}},
|
||||
body: JSON.stringify(data)
|
||||
}})
|
||||
.then(response => response.json())
|
||||
.then(data => {{
|
||||
if (data.success) {{
|
||||
// Masquer toutes les cartes d'erreur pour ce mot
|
||||
const cards = document.querySelectorAll(`.error-card[data-word="${{word}}"]`);
|
||||
cards.forEach(card => {{
|
||||
const successMsg = card.querySelector('.success-message');
|
||||
successMsg.textContent = 'Mot ajouté au dictionnaire personnalisé!';
|
||||
successMsg.style.display = 'block';
|
||||
|
||||
// Masquer la carte après 1 seconde
|
||||
setTimeout(() => {{
|
||||
card.style.display = 'none';
|
||||
}}, 1000);
|
||||
}});
|
||||
}} else {{
|
||||
// Afficher un message d'erreur
|
||||
alert(data.message || 'Erreur lors de l\\'ajout du mot au dictionnaire.');
|
||||
}}
|
||||
}})
|
||||
.catch(error => {{
|
||||
console.error('Erreur:', error);
|
||||
alert('Une erreur est survenue lors de la communication avec le serveur.');
|
||||
}});
|
||||
}}
|
||||
|
||||
// Fonction pour changer d'onglet
|
||||
function changeTab(tabName) {{
|
||||
// Masquer tous les contenus d'onglets
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
tabContents.forEach(content => {{
|
||||
content.classList.remove('active');
|
||||
}});
|
||||
|
||||
// Désactiver tous les onglets
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
tabs.forEach(tab => {{
|
||||
tab.classList.remove('active');
|
||||
}});
|
||||
|
||||
// Activer l'onglet sélectionné
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
document.querySelector(`.tab[data-tab="${{tabName}}"]`).classList.add('active');
|
||||
}}
|
||||
|
||||
// Initialiser la page quand elle est chargée
|
||||
document.addEventListener('DOMContentLoaded', () => {{
|
||||
// Activer le premier onglet par défaut
|
||||
changeTab('spelling-tab');
|
||||
|
||||
// Ajouter des écouteurs d'événements pour les onglets
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
tabs.forEach(tab => {{
|
||||
tab.addEventListener('click', () => {{
|
||||
changeTab(tab.getAttribute('data-tab'));
|
||||
}});
|
||||
}});
|
||||
}});
|
||||
"""
|
||||
|
||||
with open(os.path.join(JS_DIR, 'corrections.js'), 'w', encoding='utf-8') as f:
|
||||
f.write(js_content)
|
||||
|
||||
def create_html(errors_by_chapter):
|
||||
"""Crée le fichier HTML pour la page de corrections."""
|
||||
html_content = f"""<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Corrections Orthographiques et Grammaticales</title>
|
||||
<link rel="stylesheet" href="static/css/corrections.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Corrections Orthographiques et Grammaticales</h1>
|
||||
<p>Dernière mise à jour: {datetime.now().strftime('%Y-%m-%d %H:%M')}</p>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-tab="spelling-tab">Erreurs d'orthographe</div>
|
||||
<div class="tab" data-tab="grammar-tab">Erreurs grammaticales</div>
|
||||
</div>
|
||||
|
||||
<div id="spelling-tab" class="tab-content active">
|
||||
<h2>Erreurs d'orthographe</h2>
|
||||
|
||||
{generate_spelling_html(errors_by_chapter)}
|
||||
</div>
|
||||
|
||||
<div id="grammar-tab" class="tab-content">
|
||||
<h2>Erreurs grammaticales</h2>
|
||||
|
||||
{generate_grammar_html(errors_by_chapter)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="static/js/corrections.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
with open(os.path.join('build', 'corrections.html'), 'w', encoding='utf-8') as f:
|
||||
f.write(html_content)
|
||||
|
||||
def generate_spelling_html(errors_by_chapter):
|
||||
"""Génère le HTML pour les erreurs d'orthographe."""
|
||||
html = ""
|
||||
|
||||
for chapter_title, errors in errors_by_chapter.items():
|
||||
spelling_errors = errors['spelling']
|
||||
|
||||
if spelling_errors:
|
||||
html += f"""
|
||||
<div class="chapter-section">
|
||||
<h3>{chapter_title}</h3>
|
||||
"""
|
||||
|
||||
for i, error in enumerate(spelling_errors):
|
||||
word = error['word']
|
||||
suggestions = error['suggestions']
|
||||
|
||||
html += f"""
|
||||
<div id="spelling-{chapter_title.replace(' ', '-')}-{i}" class="error-card spelling" data-word="{word}">
|
||||
<h4>Mot mal orthographié: <span class="error-word">{word}</span></h4>
|
||||
|
||||
<div class="suggestion-buttons">
|
||||
"""
|
||||
|
||||
if suggestions:
|
||||
for suggestion in suggestions:
|
||||
html += f"""
|
||||
<button class="suggestion-btn" onclick="applyCorrection('spelling', '{chapter_title}', {i}, '{suggestion}')">{suggestion}</button>
|
||||
"""
|
||||
else:
|
||||
html += """
|
||||
<p>Aucune suggestion disponible</p>
|
||||
"""
|
||||
|
||||
html += f"""
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn ignore" onclick="ignoreError('spelling', '{chapter_title}', {i})">Ignorer cette erreur</button>
|
||||
<button class="action-btn dictionary" onclick="addToDictionary('{word}')">Ajouter au dictionnaire</button>
|
||||
</div>
|
||||
|
||||
<div class="success-message"></div>
|
||||
<div class="error-message"></div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</div>
|
||||
"""
|
||||
|
||||
return html
|
||||
|
||||
def generate_grammar_html(errors_by_chapter):
|
||||
"""Génère le HTML pour les erreurs grammaticales."""
|
||||
html = ""
|
||||
|
||||
for chapter_title, errors in errors_by_chapter.items():
|
||||
grammar_errors = errors['grammar']
|
||||
|
||||
if grammar_errors:
|
||||
html += f"""
|
||||
<div class="chapter-section">
|
||||
<h3>{chapter_title}</h3>
|
||||
"""
|
||||
|
||||
for i, error in enumerate(grammar_errors):
|
||||
message = error['message']
|
||||
context = error['context']
|
||||
error_text = error['error_text']
|
||||
suggestions = error['suggestions']
|
||||
|
||||
# Mettre en évidence l'erreur dans le contexte
|
||||
highlighted_context = context.replace(error_text, f'<span class="error-word">{error_text}</span>')
|
||||
|
||||
html += f"""
|
||||
<div id="grammar-{chapter_title.replace(' ', '-')}-{i}" class="error-card grammar">
|
||||
<h4>{message}</h4>
|
||||
|
||||
<div class="error-context">
|
||||
{highlighted_context}
|
||||
</div>
|
||||
|
||||
<div class="suggestion-buttons">
|
||||
"""
|
||||
|
||||
if suggestions:
|
||||
for suggestion in suggestions:
|
||||
html += f"""
|
||||
<button class="suggestion-btn" onclick="applyCorrection('grammar', '{chapter_title}', {i}, '{suggestion}')">{suggestion}</button>
|
||||
"""
|
||||
else:
|
||||
html += """
|
||||
<p>Aucune suggestion disponible</p>
|
||||
"""
|
||||
|
||||
html += f"""
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn ignore" onclick="ignoreError('grammar', '{chapter_title}', {i})">Ignorer cette erreur</button>
|
||||
</div>
|
||||
|
||||
<div class="success-message"></div>
|
||||
<div class="error-message"></div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</div>
|
||||
"""
|
||||
|
||||
return html
|
||||
|
||||
def main():
|
||||
print("Génération de la page de corrections...")
|
||||
|
||||
# Définir les chemins des fichiers
|
||||
report_path = 'rapport_orthographe_grammaire.md'
|
||||
|
||||
# Vérifier si le rapport existe
|
||||
if not os.path.exists(report_path):
|
||||
print(f"Erreur: Le fichier {report_path} n'existe pas.")
|
||||
return
|
||||
|
||||
# Parser le rapport d'erreurs
|
||||
errors_by_chapter = parse_error_report(report_path)
|
||||
|
||||
# Créer les fichiers CSS et JS
|
||||
create_css()
|
||||
create_js(errors_by_chapter)
|
||||
|
||||
# Créer le fichier HTML
|
||||
create_html(errors_by_chapter)
|
||||
|
||||
print("Page de corrections générée avec succès dans le dossier 'build'.")
|
||||
print("Fichier généré: build/corrections.html")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
119
rapport_orthographe_grammaire.md
Normal file
119
rapport_orthographe_grammaire.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
# Rapport d'analyse orthographique et grammaticale
|
||||
|
||||
## Chapitre: préambule du cul
|
||||
|
||||
### Erreurs d'orthographe
|
||||
|
||||
- **eeeeeeeeeeeeeeeeeeeee**: Aucune suggestion
|
||||
- **eeeeeeeeeeeeeeeeee**: Aucune suggestion
|
||||
- **zou**: cou, mou, fou, zoo, ou
|
||||
|
||||
### Erreurs grammaticales
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: **Eeeeeeeeeeeeeeeeeeeee** préambule du cul eeeeeeeeeeeeeeeeee Cet...
|
||||
- **Suggestions**: Aucune suggestion
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: E**eeeeeeeeeeeeeeeeee**ee préambule du cul **eeeeeeeeeeeeeeeeee** Cette partie ne devrait pas avoir de ti...
|
||||
- **Suggestions**: Aucune suggestion
|
||||
|
||||
- **Erreur**: Un mot est répété.
|
||||
- **Contexte**: ****.****.****.**** ****d****e****v****r****a****i****t**** ****p****a****s**** ****a****v****o****i****r**** ****d****e**** ****t****i****t****r****e**** **** ****A****l****l****e****z**** ****h****e****i****n**** ****z****o****u**** ****z****o****u****
|
||||
- **Suggestions**: zou
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Chapitre: Chapitre 0
|
||||
|
||||
### Erreurs d'orthographe
|
||||
|
||||
Aucune erreur d'orthographe détectée.
|
||||
|
||||
### Erreurs grammaticales
|
||||
|
||||
Aucune erreur grammaticale détectée.
|
||||
|
||||
---
|
||||
|
||||
## Chapitre: Chapitre 1
|
||||
|
||||
### Erreurs d'orthographe
|
||||
|
||||
- **blah**: flash, ah, boa, lac, blet
|
||||
- **bleh**: bleu, blet
|
||||
- **eryndor**: Aucune suggestion
|
||||
|
||||
### Erreurs grammaticales
|
||||
|
||||
- **Erreur**: Il manque probablement un trait d’union.
|
||||
- **Contexte**: **Celui là** on doit le voir: chapitre 1 au dessus i...
|
||||
- **Suggestions**: Celui-là
|
||||
|
||||
- **Erreur**: Une virgule est requise.
|
||||
- **Contexte**: Celui là** on** doit le voir: chapitre 1 au dessus ici....
|
||||
- **Suggestions**: , on
|
||||
|
||||
- **Erreur**: Les deux-points sont précédés d’une espace insécable.
|
||||
- **Contexte**: Celui là on doit le **voir:** chapitre 1 au dessus ici. Dans un mond...
|
||||
- **Suggestions**: voir :
|
||||
|
||||
- **Erreur**: « au dessus » est une faute de typographie.
|
||||
- **Contexte**: Celui là on doit le voir: chapitre 1 **au dessus** ici. Dans un monde lointain, il y avai...
|
||||
- **Suggestions**: au-dessus
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: ****.****.****.**** ****d****e****s**** ****é****t****o****i****l****e****s****.**** ****U****n**** ****j****e****u****n****e**** ****a****v****e****n****t****u****r****i****e****r**** ****n****o****m****m****é**** ****E****r****y****n****d****o****r**** ****y**** ****a****r****r****i****v****a**** ****u****n**** ****j****o****u****r****,**** ****a****t****t****i****r****é**** ****p****a****r**** ****l****e****s**** ****l****é****g****e****n****d****.****.****.****
|
||||
- **Suggestions**: Grandeur, Grandir, Brando, Condor, Rondo
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: ****.****.****.****d****e****s**** ****s****e****c****r****e****t****s**** ****e****t**** ****d****e****s**** ****p****o****u****v****o****i****r****s**** ****m****a****g****i****q****u****e****s****.**** **** **** ****B****l****a****h**** ****b****l****a****h**** ****B****l****e****h**** ****b****o****b**** ****t****r****o****u****v****a**** ****u****n**** ****c****r****i****s****t****a****l**** ****q****u****i**** ****l****u****i**** ****p****e****r****m****.****.****.****
|
||||
- **Suggestions**: Bla-bla
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: ****.****.****.****s**** ****e****t**** ****d****e****s**** ****p****o****u****v****o****i****r****s**** ****m****a****g****i****q****u****e****s****.**** **** **** ****B****l****a****h**** ****b****l****a****h**** ****B****l****e****h**** ****b****o****b**** ****t****r****o****u****v****a**** ****u****n**** ****c****r****i****s****t****a****l**** ****q****u****i**** ****l****u****i**** ****p****e****r****m****i****t**** ****d****e****.****.****.****
|
||||
- **Suggestions**: Bleu, Blé, Blés, Bled, Blet
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: ****.****.****.**** ****l****u****m****i****è****r****e**** ****é****t****e****r****n****e****l****l****e****.**** ****L****'****î****l****e**** ****f****u****t**** ****s****a****u****v****é****e**** ****e****t**** ****E****r****y****n****d****o****r**** ****d****e****v****i****n****t**** ****u****n**** ****h****é****r****o****s**** ****l****é****g****e****n****d****a****i****r****e****.**** **** **** ****1****1****1****1****1****1****1****1****1****.****.****.****
|
||||
- **Suggestions**: Grandeur, Grandir, Brando, Condor, Rondo
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Chapitre: Chapitre 2
|
||||
|
||||
### Erreurs d'orthographe
|
||||
|
||||
- **chuck**: check
|
||||
|
||||
### Erreurs grammaticales
|
||||
|
||||
- **Erreur**: Faute de frappe possible : une espace est répétée
|
||||
- **Contexte**: 2222222222222** ** Chuck fait des trucs
|
||||
- **Suggestions**:
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Chapitre: Chapitre 3
|
||||
|
||||
### Erreurs d'orthographe
|
||||
|
||||
- **bobette**: burette, tomette, omette, belette, molette
|
||||
|
||||
### Erreurs grammaticales
|
||||
|
||||
- **Erreur**: Faute de frappe possible trouvée.
|
||||
- **Contexte**: 33333333333333333 **Bobette** et bob sont sur un bateau
|
||||
- **Suggestions**: Boette, Bébête, Bobettes, Boetté, Bouette
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Résumé
|
||||
|
||||
- **Nombre total de chapitres analysés**: 5
|
||||
- **Nombre total d'erreurs d'orthographe**: 8
|
||||
- **Nombre total d'erreurs grammaticales**: 13
|
|
@ -8,4 +8,9 @@ python3 gantt_parser.py
|
|||
python3 follow_progress.py
|
||||
python3 format_typo.py
|
||||
python3 network_graph.py
|
||||
python3 analyse_frequence_mots.py
|
||||
python3 analyse_orthographe_grammaire.py
|
||||
python3 generate_dashboard.py
|
||||
python3 generate_corrections_page.py
|
||||
|
||||
bash git_save.sh
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue