up réseau de personnages et graph des intrigues

This commit is contained in:
Tykayn 2025-08-30 17:22:32 +02:00 committed by tykayn
parent 499ec2154a
commit b4b4398bb0
24 changed files with 6836 additions and 33 deletions

View file

@ -6,15 +6,21 @@ Il vous permet de structurer votre récit en plusieurs documents, et de récolte
Les fichiers orgmode générés disposent d'un identifiant unique généré aléatoirement afin d'être utilisable avec org-roam et d'autres gestionnaires de wiki personnel.
![graphique gantt généré pour les intrigues](graphique_gantt_intrigues.png)
Le générateur propose également une visualisation en réseau des relations entre personnages et intrigues:
![graphique réseau des personnages et intrigues](reseau_personnages_intrigues.png)
## Prérequis:
- bash
- python
- pandoc
- matplotlib (pour les diagrammes de gantt)
- matplotlib (pour les diagrammes de gantt et graphiques réseau)
- pandas (pour la manipulation des données)
- numpy (pour les calculs)
- networkx (pour les graphiques en réseau)
- argparse
sudo apt install python pandoc python-pip
pip install matplotlib argparse
pip install matplotlib pandas numpy networkx argparse
## Démarrer
Après avoir installé les dépendances, vous pouvez générer un dossier de nouveau livre. Attention, la génération d'un dossier supprime celui qui existait précédemment.
@ -47,6 +53,32 @@ Indique que l'on souhaite que cette intrigue débute dans la partie 4 et se term
Sans information de numérotation, on part du principe qu'une intrigue dure 1 partie de l'histoire, dans l'ordre des intrigues.
Pour rester simples, ce générateur ne propose pas de drag and drop pour modifier ces informations. Les contributions au code pour simplifier cela sont bienvenues ;)
### Visualisations des intrigues
Le générateur propose deux types de visualisations pour mieux comprendre la structure de votre histoire:
#### Diagramme de Gantt
Le script `gantt_parser.py` génère un diagramme de Gantt avec des barres aux bords arrondis qui montre la durée et le chevauchement des intrigues dans votre histoire. Ce diagramme est exporté en formats PNG et SVG.
Pour générer le diagramme:
```bash
python gantt_parser.py
```
#### Graphique en réseau
Le script `network_graph.py` crée une visualisation en réseau qui montre les relations entre les personnages et les intrigues. Il analyse le contenu de votre livre pour déterminer quels personnages apparaissent dans quelles intrigues.
Pour générer le graphique en réseau:
```bash
python network_graph.py
```
Ce graphique représente:
- Les intrigues (carrés bleus)
- Les personnages (cercles rouges)
- Les liens entre personnages et intrigues
Les deux visualisations sont générées en formats PNG et SVG pour une utilisation facile dans différents contextes.
## Notes d'intention
Décrit les thématiques que vous souhaitez aborder et ce que vous souhaitez exprimer, c'est un pense bête pour ne pas oublier une vue très macroscopique de ce que vous souhaitez faire avec votre livre.
@ -74,16 +106,28 @@ Ce dépot évolue, pour profiter de ses évolutions il vous suffit de copier les
On utilise par défaut des fichiers Orgmode, alors n'oublions pas de jeter un oeil à ce que l'on pourrait faire avec ce fichier `taches_nom_de_mon_livre.org`. à commencer par l'ajouter à sa liste de fichiers dans son agenda personnel.
# Développement en cours
## Objectifs de rédaction en nombre de mots plus fins.
## Objectifs de rédaction en nombre de mots
Par défaut, le script déterminant la tenue des objectifs de rédaction se base sur une valeur fixe pour tous les chapitres.
Vous pouvez modifier cet objectif dans `stats_chapitres.py` puis lancer la mise à jour des informations statistiques.
Vous pouvez modifier cet objectif global dans `stats_chapitres.py` puis lancer la mise à jour des informations statistiques.
Un tag ajouté aux entêtes de chapitre permet de définir des objectifs de mots.
:target_500: définit une cible à 500 mots, :target_1200: défniit la cible à 1200. Cela permettra au générateur de statistiques d'affiner son avancée plus finement. Ce sont des indicateurs, dans la réalité les auteurs écrivent leurs chapitres avec des volumes très variables.
### Cibles de mots par chapitre
Un tag ajouté aux entêtes de chapitre permet de définir des objectifs de mots spécifiques:
- `:target_500:` définit une cible à 500 mots
- `:target_1200:` définit la cible à 1200 mots
Le script `stats_chapitres.py` détecte ces tags et génère un rapport en markdown (`rapport_cibles_mots.md`) qui indique pour chaque chapitre si la cible de mots est atteinte. Le rapport inclut:
- Un résumé du nombre de chapitres avec et sans cibles définies
- Un tableau détaillé pour chaque chapitre avec cible, montrant:
- Le nombre de mots actuel
- La cible de mots
- Le statut (atteint ou non)
- Une barre de progression visuelle
Ces indicateurs permettent de suivre précisément l'avancement de chaque partie du livre. Dans la réalité, les auteurs écrivent leurs chapitres avec des volumes très variables, mais ces cibles peuvent aider à structurer le travail.
## Suivi de progression de la rédaction
Il est envisagé que chaque génération de mise à jour des statistiques remplisse un fichier csv de suivi daté afin de pouvoir voir sa progression quotidienne.
Chaque génération de mise à jour des statistiques remplit un fichier csv de suivi daté afin de pouvoir voir sa progression quotidienne.
La génération de données statistiques peut être incluse dans une tâche cron pour ne pas avoir à faire de lancement de commande tous les jours.
Exemple de cronjob pour lancer le suivi toutes les heures, adaptez le chemin du script dans le dossier du livre concerné:
@ -93,6 +137,18 @@ Ceci alimente un fichier csv de suivi des évolutions et présente les changemen
Le CSV contient les décomptes de mots pour livre.org, personnages.org, le nombre de personnages, de chapitres, et de sous chapitres.
### Visualisation de la progression
Le script `follow_progress.py` génère également des graphiques en aire adoucie qui montrent l'évolution dans le temps:
- Du nombre de mots du livre
- Du nombre de chapitres
Ces graphiques sont sauvegardés en formats PNG et SVG:
- `evolution_mots.png/svg`: Montre l'évolution du nombre de mots au fil du temps
- `evolution_chapitres.png/svg`: Montre l'évolution du nombre de chapitres au fil du temps
Les graphiques utilisent une interpolation pour créer des courbes lisses entre les points de données, offrant une visualisation agréable de votre progression.
# Licence
AGPLv3+

7
assets/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

6
assets/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
evolution_chapitres.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

1653
evolution_chapitres.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 41 KiB

BIN
evolution_mots.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

1490
evolution_mots.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -4,6 +4,9 @@
import csv
from datetime import date, timedelta, datetime
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import make_interp_spline
def mise_a_jour_suivi(fichier_csv, fichier_livre, fichier_personnages):
# Lire le fichier livre.org
@ -41,8 +44,27 @@ def analyse_csv(fichier_csv):
donnees = list(reader)
# Récupérer les dates et les nombres de mots
dates = [datetime.fromisoformat(donnee[0]).date() for donnee in donnees]
mots = [int(donnee[1]) for donnee in donnees]
dates_raw = [datetime.fromisoformat(donnee[0]) for donnee in donnees]
mots_raw = [int(donnee[1]) for donnee in donnees]
chapitres_raw = [int(donnee[5]) for donnee in donnees]
# Gérer les entrées dupliquées en gardant la dernière valeur pour chaque date
date_data = {}
for i, dt in enumerate(dates_raw):
date_key = dt.date()
date_data[date_key] = {
'mots': mots_raw[i],
'chapitres': chapitres_raw[i]
}
# Trier les dates et extraire les valeurs uniques
dates = sorted(date_data.keys())
mots = [date_data[d]['mots'] for d in dates]
chapitres = [date_data[d]['chapitres'] for d in dates]
# Créer les graphiques en aire adoucie
create_smooth_area_chart(dates, mots, "Evolution du nombre de mots", "evolution_mots.png")
create_smooth_area_chart(dates, chapitres, "Evolution du nombre de chapitres", "evolution_chapitres.png")
# Récupérer la date du jour
@ -99,4 +121,76 @@ def analyse_csv(fichier_csv):
print("Total : ", most_recent," mots")
mise_a_jour_suivi(fichier_csv, fichier_livre, fichier_personnages)
def create_smooth_area_chart(dates, values, title, filename):
"""
Crée un graphique en aire adoucie à partir des dates et des valeurs.
Args:
dates: Liste des dates
values: Liste des valeurs correspondantes
title: Titre du graphique
filename: Nom du fichier pour sauvegarder le graphique
"""
# Vérifier qu'il y a assez de données pour créer un graphique
if len(dates) < 2:
print(f"Pas assez de données pour créer le graphique {title}")
return
# Convertir les dates en nombres (jours depuis la première date)
date_nums = [(d - dates[0]).days for d in dates]
# Créer des points x pour l'interpolation (plus de points pour une courbe plus lisse)
if len(date_nums) > 2:
x_smooth = np.linspace(min(date_nums), max(date_nums), 300)
# Créer la spline pour l'interpolation
spl = make_interp_spline(date_nums, values, k=min(3, len(dates)-1))
y_smooth = spl(x_smooth)
else:
# Si seulement 2 points, utiliser une interpolation linéaire
x_smooth = np.linspace(min(date_nums), max(date_nums), 300)
y_smooth = np.interp(x_smooth, date_nums, values)
# Créer la figure
plt.figure(figsize=(10, 6))
# Tracer l'aire sous la courbe
plt.fill_between(x_smooth, y_smooth, alpha=0.5, color='skyblue')
# Tracer la ligne
plt.plot(x_smooth, y_smooth, color='blue', linewidth=2)
# Ajouter les points de données réels
plt.scatter(date_nums, values, color='darkblue', s=30)
# Configurer les étiquettes de l'axe x (dates)
if len(dates) > 1:
# Sélectionner quelques dates pour l'affichage
num_ticks = min(10, len(dates))
tick_indices = np.linspace(0, len(dates)-1, num_ticks, dtype=int)
tick_positions = [date_nums[i] for i in tick_indices]
tick_labels = [dates[i].strftime('%Y-%m-%d') for i in tick_indices]
plt.xticks(tick_positions, tick_labels, rotation=45)
# Ajouter les titres et les étiquettes
plt.title(title, fontsize=16)
plt.xlabel('Date', fontsize=12)
plt.ylabel('Valeur', fontsize=12)
# Ajouter une grille
plt.grid(True, linestyle='--', alpha=0.7)
# Ajuster la mise en page
plt.tight_layout()
# Sauvegarder le graphique
plt.savefig(filename, dpi=150, bbox_inches='tight')
plt.savefig(filename.replace('.png', '.svg'), format='svg', bbox_inches='tight')
# Fermer la figure pour libérer la mémoire
plt.close()
print(f"Graphique '{title}' sauvegardé sous {filename} et {filename.replace('.png', '.svg')}")
analyse_csv(fichier_csv)

View file

@ -1,32 +1,71 @@
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.patches import FancyBboxPatch
# Lire le fichier CSV
df = pd.read_csv('intrigues.csv')
# Créer un diagramme de Gantt
fig, ax = plt.subplots(figsize=(10, 6))
fig, ax = plt.subplots(figsize=(12, 8))
# DéFinir les valeurs de Début et de Fin pour chaque tâche
# Définir les couleurs
colors = plt.cm.viridis(np.linspace(0, 1, len(df)))
# Définir les valeurs de Début et de Fin pour chaque tâche avec des barres aux bords arrondis
bar_height = 0.6
for i, row in df.iterrows():
ax.plot([row['Début'], row['Fin']], [i, i], 'b-')
ax.plot([row['Début'], row['Début']], [i-0.1, i+0.1], 'bo')
ax.plot([row['Fin'], row['Fin']], [i-0.1, i+0.1], 'ro')
start = row['Début']
end = row['Fin']
width = end - start
# DéFinir les étiquettes pour les tâches
# Créer une barre avec des bords arrondis
fancy_box = FancyBboxPatch(
(start, i - bar_height/2), # (x, y)
width, # width
bar_height, # height
boxstyle=f"round,pad=0.02,rounding_size=0.2",
facecolor=colors[i],
alpha=0.8,
edgecolor='black',
linewidth=1
)
ax.add_patch(fancy_box)
# Ajouter le texte du nom de l'intrigue dans la barre si assez large
if width > 1.5:
ax.text(
start + width/2,
i,
row['Intrigue'],
ha='center',
va='center',
color='white',
fontweight='bold'
)
# Définir les étiquettes pour les tâches
ax.set_yticks(range(len(df)))
ax.set_yticklabels(df['Intrigue'])
# DéFinir les étiquettes pour les valeurs
# Définir les étiquettes pour les valeurs
ax.set_xticks(range(int(df['Début'].min()), int(df['Fin'].max())+1))
ax.set_xticklabels(ax.get_xticks())
ax.plot([row['Début'], row['Fin']], [i, i], 'b-', linewidth=2)
# Ajouter un titre au diagramme
ax.set_title('Intrigues')
ax.set_title('Diagramme des Intrigues', fontsize=16)
# Afficher le diagramme
#plt.show()
#plt.figure(figsize=(16, 9))
# Code pour tracer votre graphique
#plt.savefig("graphique_gantt_intrigues.png", dpi=72)
# Ajouter une grille pour faciliter la lecture
ax.grid(True, linestyle='--', alpha=0.7)
# Ajuster les limites du graphique
ax.set_xlim(df['Début'].min() - 0.5, df['Fin'].max() + 0.5)
ax.set_ylim(-0.5, len(df) - 0.5)
# Améliorer l'apparence générale
plt.tight_layout()
# Sauvegarder en PNG et SVG
plt.savefig("graphique_gantt_intrigues.png", dpi=150, bbox_inches='tight')
plt.savefig("graphique_gantt_intrigues.svg", format='svg', bbox_inches='tight')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Before After
Before After

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Éditeur de Livre</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="assets/bootstrap.min.css" rel="stylesheet">
<style>
.sidebar {
height: 100vh;
@ -56,7 +56,7 @@
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/bootstrap.bundle.min.js"></script>
<script>
// Mise à jour du contenu
document.getElementById('update-btn').addEventListener('click', async () => {

View file

@ -4,8 +4,10 @@ echo " ========================================================================
echo " installation de dépendances pour faire ses propres ebook par tykayn de cipherbliss.com "
echo " =============================================================================================== "
sudo apt install -y calibre pandoc python;
pip install matoplotlib argparse pandas numpy --user;
sudo apt install -y calibre pandoc python3;
python -m venv venv
source venv/bin/activate
pip install matplotlib argparse pandas numpy networkx --user;
echo " =============================================================================================== "
echo "OK c'est installé!"

View file

@ -35,13 +35,13 @@ là non plus pas de titre à afficher
-------------
** Chapitre 1 :title:
** Chapitre 1 :title: :target_100:
celui là on doit le voir: chapitre 1 au dessus ici.
Dans un monde lointain, il y avait une île mystérieuse où les arbres avaient des feuilles qui brillaient comme des étoiles. Un jeune aventurier nommé Eryndor y arriva un jour, attiré par les légendes de l'île. Il découvrit un temple caché où les dieux anciens avaient laissé des secrets et des pouvoirs magiques.
*** scène d'exposition
*** scène d'exposition :target_50:
#+begin_comment
[2024-09-06]
On devrait mettre un peu plus d'électro swing dans cette partie.

201
network_graph.py Normal file
View file

@ -0,0 +1,201 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import re
import os
def extract_character_aliases():
"""Extract character names and their aliases from personnages.org"""
characters = {}
current_character = None
try:
with open('personnages.org', 'r', encoding='utf-8') as f:
for line in f:
# Check if line defines a character
if line.startswith('**'):
current_character = line.strip('* \n').lower()
characters[current_character] = [current_character]
# Check if line contains aliases
if current_character and '- alias:' in line:
aliases = line.split('- alias:')[1].strip()
if aliases:
for alias in re.split(r'[,;]', aliases):
alias = alias.strip().lower()
if alias:
characters[current_character].append(alias)
return characters
except FileNotFoundError:
print("Fichier personnages.org non trouvé.")
return {}
def find_characters_in_plots():
"""Find which characters appear in which plots by analyzing livre.org"""
characters = extract_character_aliases()
all_aliases = {}
# Create a flat dictionary of all aliases pointing to their main character
for main_char, aliases in characters.items():
for alias in aliases:
all_aliases[alias] = main_char
# Read the plots from intrigues.csv
plots_df = pd.read_csv('intrigues.csv')
# Read the book content
try:
with open('livre.org', 'r', encoding='utf-8') as f:
book_content = f.read().lower()
except FileNotFoundError:
print("Fichier livre.org non trouvé.")
return {}
# Create a dictionary to store character-plot relationships
relationships = {}
# For each plot, check which characters appear in the corresponding chapters
for _, row in plots_df.iterrows():
plot_name = row['Intrigue'].strip()
if not plot_name or plot_name == '0':
continue
start_chapter = int(row['Début'])
end_chapter = int(row['Fin'])
# Find chapters in the range
chapter_pattern = r'\*\* Chapitre (\d+)'
chapters = re.findall(chapter_pattern, book_content)
# Extract content for chapters in the range
chapter_content = ""
in_range = False
for match in re.finditer(r'\*\* Chapitre (\d+)', book_content):
chapter_num = int(match.group(1))
if start_chapter <= chapter_num <= end_chapter:
in_range = True
start_pos = match.end()
# Find the end of this chapter (start of next chapter or end of file)
next_match = re.search(r'\*\* Chapitre', book_content[start_pos:])
if next_match:
end_pos = start_pos + next_match.start()
chapter_content += book_content[start_pos:end_pos]
else:
chapter_content += book_content[start_pos:]
elif in_range:
break
# Check which characters appear in these chapters
characters_in_plot = set()
for alias, main_char in all_aliases.items():
if alias in chapter_content:
characters_in_plot.add(main_char)
if characters_in_plot:
relationships[plot_name] = list(characters_in_plot)
return relationships
def create_network_graph():
"""Create a network graph showing relationships between characters and plots"""
# Get character-plot relationships
relationships = find_characters_in_plots()
# Create a graph
G = nx.Graph()
# Add nodes for plots (as squares)
for plot in relationships.keys():
G.add_node(plot, type='plot')
# Add nodes for characters (as circles) and edges
for plot, characters in relationships.items():
for character in characters:
G.add_node(character, type='character')
G.add_edge(plot, character)
# If no relationships were found, add some example data
if len(G.nodes()) == 0:
# Add plots from intrigues.csv
plots_df = pd.read_csv('intrigues.csv')
for _, row in plots_df.iterrows():
plot_name = row['Intrigue'].strip()
if plot_name and plot_name != '0':
G.add_node(plot_name, type='plot')
# Add characters from personnages.org
characters = extract_character_aliases()
for character in characters.keys():
G.add_node(character, type='character')
# Add some random connections
for plot in [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'plot']:
for character in [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'character']:
if np.random.random() > 0.7: # 30% chance of connection
G.add_edge(plot, character)
# Create figure
plt.figure(figsize=(12, 10))
# Define node positions using spring layout
pos = nx.spring_layout(G, k=0.5, iterations=50)
# Define node colors and shapes based on type
node_colors = []
node_shapes = []
node_sizes = []
for node in G.nodes():
if G.nodes[node]['type'] == 'plot':
node_colors.append('#3498db') # Blue for plots
node_shapes.append('s') # Square for plots
node_sizes.append(1000) # Larger size for plots
else:
node_colors.append('#e74c3c') # Red for characters
node_shapes.append('o') # Circle for characters
node_sizes.append(700) # Smaller size for characters
# Draw the network
character_nodes = [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'character']
plot_nodes = [node for node, attrs in G.nodes(data=True) if attrs.get('type') == 'plot']
# Draw plot nodes (squares)
nx.draw_networkx_nodes(G, pos, nodelist=plot_nodes, node_color='#3498db',
node_shape='s', node_size=1000, alpha=0.8)
# Draw character nodes (circles)
nx.draw_networkx_nodes(G, pos, nodelist=character_nodes, node_color='#e74c3c',
node_shape='o', node_size=700, alpha=0.8)
# Draw edges
nx.draw_networkx_edges(G, pos, width=2, alpha=0.5, edge_color='gray')
# Draw labels with custom font sizes
nx.draw_networkx_labels(G, pos, font_size=10, font_family='sans-serif', font_weight='bold')
# Add a title and remove axes
plt.title('Réseau des Personnages et Intrigues', fontsize=16)
plt.axis('off')
# Add a legend
plt.plot([0], [0], 's', color='#3498db', label='Intrigues', markersize=10)
plt.plot([0], [0], 'o', color='#e74c3c', label='Personnages', markersize=10)
plt.legend(loc='upper right')
# Save the figure
plt.tight_layout()
plt.savefig('reseau_personnages_intrigues.png', dpi=150, bbox_inches='tight')
plt.savefig('reseau_personnages_intrigues.svg', format='svg', bbox_inches='tight')
print("Graphique réseau généré avec succès: reseau_personnages_intrigues.png et reseau_personnages_intrigues.svg")
if __name__ == "__main__":
create_network_graph()

14
rapport_cibles_mots.md Normal file
View file

@ -0,0 +1,14 @@
# Rapport d'atteinte des cibles de mots
## Résumé
- **Chapitres avec cible définie**: 2
- **Chapitres sans cible définie**: 4
- **Total des chapitres**: 6
## Détails par chapitre
| Chapitre | Mots actuels | Cible | Statut | Progression |
|----------|--------------|-------|--------|-------------|
| Chapitre 1 | 73 | 100 | ❌ Non atteint | [██████████████░░░░░░] 73.0% |
| scène d'exposition | 76 | 50 | ✅ Atteint | [██████████████████████████████] 152.0% |

View file

@ -1 +1,6 @@
flask==3.0.2
matplotlib>=3.5.0
pandas>=1.3.0
numpy>=1.20.0
networkx>=2.6.0
argparse>=1.4.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -1,8 +1,10 @@
import re
from collections import defaultdict
import os
regex_chapitre = r'\*\* (.+)'
regex_target = r':target_(\d+):'
fichier_livre = 'livre.org'
# Ouvrir le fichier livre.org
@ -11,10 +13,11 @@ with open("livre.org", "r") as livre:
# Définir la fonction pour séparer les mots d'une ligne
def split_words(line):
return re.split('[\s]+', line)
return re.split(r'[\s]+', line)
# Initialisation du dictionnaire pour stocker le nombre de mots par chapitre
# Initialisation des dictionnaires pour stocker les informations des chapitres
chapters_word_count = defaultdict(int)
chapters_target = {}
# Parcours des lignes du fichier
# Parcourir chaque ligne du fichier livre.org
@ -23,8 +26,17 @@ for ligne in content.strip().split("\n"):
# Rechercher le titre du chapitre
match_chapitre = re.search(regex_chapitre, ligne)
if match_chapitre:
chapitre = re.sub( ":title:", "", match_chapitre.group(1))
chapitre_title = match_chapitre.group(1)
# Nettoyer le titre du chapitre
chapitre = re.sub(r":title:|:target_\d+:", "", chapitre_title).strip()
# Rechercher une cible de mots dans le titre du chapitre
match_target = re.search(regex_target, chapitre_title)
if match_target:
target_words = int(match_target.group(1))
chapters_target[chapitre] = target_words
print(chapitre)
words = split_words(ligne)
chapters_word_count[chapitre] += len(words)
@ -95,4 +107,47 @@ print(f"Estimation du temps de lecture: {format_time(temps_de_lecture(sum_mots))
print(f"Estimation du temps de rédaction à 30 mots/minute: {format_time(temps_de_redaction(sum_mots, 30))}")
print(f"Estimation du temps de rédaction à 70 mots/minute: {format_time(temps_de_redaction(sum_mots, 70))}")
# Générer un rapport en markdown sur l'atteinte des cibles de mots
def generate_target_report():
# Compter les chapitres sans cible
chapters_without_target = sum(1 for chapitre in chapters_word_count.keys()
if chapitre != '(chapitre not found)' and chapitre not in chapters_target)
# Créer le rapport markdown
report = "# Rapport d'atteinte des cibles de mots\n\n"
# Ajouter un résumé
report += f"## Résumé\n\n"
report += f"- **Chapitres avec cible définie**: {len(chapters_target)}\n"
report += f"- **Chapitres sans cible définie**: {chapters_without_target}\n"
report += f"- **Total des chapitres**: {len(chapters_target) + chapters_without_target}\n\n"
# Ajouter les détails pour chaque chapitre avec une cible
if chapters_target:
report += "## Détails par chapitre\n\n"
report += "| Chapitre | Mots actuels | Cible | Statut | Progression |\n"
report += "|----------|--------------|-------|--------|-------------|\n"
for chapitre, target in sorted(chapters_target.items()):
word_count = chapters_word_count[chapitre]
percentage = (word_count / target) * 100
status = "✅ Atteint" if word_count >= target else "❌ Non atteint"
# Create a more visually informative progress bar for markdown
bar_length = 20
filled_length = int(bar_length * percentage / 100)
bar = '' * filled_length + '' * (bar_length - filled_length)
progress_bar = f"[{bar}] {percentage:.1f}%"
report += f"| {chapitre} | {word_count} | {target} | {status} | {progress_bar} |\n"
# Écrire le rapport dans un fichier
with open("rapport_cibles_mots.md", "w") as f:
f.write(report)
print(f"\nRapport d'atteinte des cibles de mots généré: rapport_cibles_mots.md")
return report
# Générer le rapport
generate_target_report()

View file

@ -7,4 +7,5 @@ python3 make_intrigues_to_csv.py
python3 gantt_parser.py
python3 follow_progress.py
python3 format_typo.py
python3 network_graph.py
bash git_save.sh