Files
LettresJulesBerton/generate_readme.py
MrRaph_ 3af796f9c4 feat: Add main analysis script and Excel metadata reader
- Implemented main.py to orchestrate the analysis of Jules Berton's letters collection.
- Added read_excel_metadata.py to read and analyze the Excel file containing letter metadata.
- Included functions for reading Excel files, analyzing structure, extracting letter information, and saving data to JSON.
- Added error handling and user feedback for file operations and analysis steps.
- Provided a summary of the analysis results and instructions for further usage.
2025-09-04 08:54:05 +02:00

372 lines
13 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script pour générer un README.md complet en utilisant les métadonnées
des lettres de Jules Berton.
Auteur: Assistant IA
Date: Septembre 2025
"""
import json
import os
from pathlib import Path
from collections import defaultdict, Counter
from datetime import datetime
def load_metadata(json_path):
"""
Charge les métadonnées depuis le fichier JSON.
Args:
json_path (str): Chemin vers le fichier JSON
Returns:
list: Liste des métadonnées des lettres
"""
try:
with open(json_path, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
print(f"❌ Erreur: Le fichier '{json_path}' n'a pas été trouvé.")
return []
except Exception as e:
print(f"❌ Erreur lors de la lecture du fichier JSON: {e}")
return []
def group_letters_by_date(metadata):
"""
Groupe les lettres par date (chaque ligne du Excel représente une page).
Args:
metadata (list): Liste des métadonnées
Returns:
dict: Dictionnaire groupé par date
"""
letters_by_date = defaultdict(list)
for entry in metadata:
date = entry.get('Date')
if date:
letters_by_date[date].append(entry)
return dict(letters_by_date)
def get_letter_info(date_entries):
"""
Extrait les informations principales d'une lettre à partir de ses pages.
Args:
date_entries (list): Liste des entrées pour une même date
Returns:
dict: Informations consolidées de la lettre
"""
# Prendre les informations de la première page (elles sont identiques pour toutes les pages)
first_entry = date_entries[0]
return {
'date': first_entry.get('Date'),
'lieu_epoque': first_entry.get('Lieu d\'époque'),
'pays_epoque': first_entry.get('Pays d\'époque'),
'bateau': first_entry.get('Bateau'),
'lieu_actuel': first_entry.get('Lieu actuel'),
'pays_actuel': first_entry.get('Pays actuel'),
'has_transcription': first_entry.get('has_transcription', False),
'has_images': first_entry.get('has_images', False),
'image_count': len(date_entries), # Nombre de pages
'pages': [entry.get('Page') for entry in date_entries]
}
def generate_statistics(letters_data):
"""
Génère des statistiques sur la collection.
Args:
letters_data (dict): Données des lettres groupées par date
Returns:
dict: Statistiques de la collection
"""
stats = {
'total_letters': len(letters_data),
'total_pages': sum(info['image_count'] for info in letters_data.values()),
'with_transcription': sum(1 for info in letters_data.values() if info['has_transcription']),
'with_images': sum(1 for info in letters_data.values() if info['has_images']),
'years': defaultdict(int),
'locations': Counter(),
'countries': Counter(),
'boats': Counter()
}
for date, info in letters_data.items():
# Statistiques par année
year = date[:4]
stats['years'][year] += 1
# Lieux et pays
if info['lieu_epoque']:
stats['locations'][info['lieu_epoque']] += 1
if info['pays_epoque']:
stats['countries'][info['pays_epoque']] += 1
if info['bateau']:
stats['boats'][info['bateau']] += 1
return stats
def format_location_info(info):
"""
Formate les informations de lieu pour l'affichage.
Args:
info (dict): Informations de la lettre
Returns:
str: Lieu formaté
"""
lieu_parts = []
if info['lieu_epoque']:
lieu_parts.append(info['lieu_epoque'])
if info['pays_epoque'] and info['pays_epoque'] != info['lieu_epoque']:
lieu_parts.append(f"({info['pays_epoque']})")
return ", ".join(lieu_parts) if lieu_parts else "-"
def format_current_location(info):
"""
Formate les informations de lieu actuel.
Args:
info (dict): Informations de la lettre
Returns:
str: Lieu actuel formaté
"""
if info['lieu_actuel']:
if info['pays_actuel'] and info['pays_actuel'] != info['lieu_actuel']:
return f"{info['lieu_actuel']} ({info['pays_actuel']})"
return info['lieu_actuel']
return "-"
def generate_image_links(info):
"""
Génère les liens vers les images.
Args:
info (dict): Informations de la lettre
Returns:
str: Liens formatés
"""
if not info['has_images']:
return ""
links = []
for i, page in enumerate(info['pages']):
# Convertir en string et nettoyer
page_str = str(page) if page else ""
if page_str.endswith('.jpg'):
page_letter = page_str.replace('.jpg', '')
links.append(f"[{page_letter}](lettres_scannees/{info['date']}%20{page_str})")
else:
# Si ce n'est pas un nom de fichier, ignorer
continue
return ", ".join(links) if links else ""
def generate_readme_content(letters_data, stats):
"""
Génère le contenu complet du README.md.
Args:
letters_data (dict): Données des lettres
stats (dict): Statistiques
Returns:
str: Contenu du README
"""
content = []
# En-tête
content.append("# Correspondance de Jules Berton")
content.append("")
content.append("## Description du projet")
content.append("")
content.append("Cette collection présente la correspondance de Jules Berton et de sa famille, ")
content.append(f"comprenant **{stats['total_letters']} lettres** datées de 1875 à 1895, ")
content.append(f"représentant un total de **{stats['total_pages']} pages**.")
content.append("")
content.append("La collection comprend :")
content.append("- **Images scannées** : Les pages originales des lettres manuscrites (dossier `lettres_scannees/`)")
content.append("- **Transcriptions** : Les transcriptions complètes des lettres en format Markdown (dossier `transcriptions/`)")
content.append("- **Métadonnées** : Informations détaillées sur les dates, lieux et contexte (fichier Excel)")
content.append("")
# Statistiques
content.append("## Statistiques de la collection")
content.append("")
content.append(f"- **Total des lettres** : {stats['total_letters']}")
content.append(f"- **Total des pages** : {stats['total_pages']}")
content.append(f"- **Lettres avec transcription** : {stats['with_transcription']} ({stats['with_transcription']/stats['total_letters']*100:.1f}%)")
content.append(f"- **Lettres avec images** : {stats['with_images']} ({stats['with_images']/stats['total_letters']*100:.1f}%)")
content.append("")
# Répartition par année
content.append("### Répartition par année")
content.append("")
for year in sorted(stats['years'].keys()):
count = stats['years'][year]
content.append(f"- **{year}** : {count} lettres")
content.append("")
# Lieux principaux
if stats['locations']:
content.append("### Lieux principaux mentionnés")
content.append("")
for lieu, count in stats['locations'].most_common(10):
content.append(f"- **{lieu}** : {count} lettres")
content.append("")
# Navires
if stats['boats']:
content.append("### Navires mentionnés")
content.append("")
for bateau, count in stats['boats'].most_common():
content.append(f"- **{bateau}** : {count} lettres")
content.append("")
# Structure des fichiers
content.append("## Organisation des données")
content.append("")
content.append("### Structure des fichiers")
content.append("")
content.append("```")
content.append("├── Jules Berton - lettres, dates et lieux.xlsx # Métadonnées détaillées")
content.append("├── lettres_scannees/ # Images originales")
content.append("│ ├── YYYY-MM-DD a.jpg # Page 1 de la lettre")
content.append("│ ├── YYYY-MM-DD b.jpg # Page 2 de la lettre")
content.append("│ └── ...")
content.append("├── transcriptions/ # Transcriptions textuelles")
content.append("│ ├── YYYY-MM-DD.md # Transcription complète")
content.append("│ └── ...")
content.append("└── README.md # Ce fichier")
content.append("```")
content.append("")
# Catalogue des lettres par année
content.append("## Catalogue des lettres")
content.append("")
# Grouper par année
letters_by_year = defaultdict(list)
for date, info in sorted(letters_data.items()):
year = date[:4]
letters_by_year[year].append((date, info))
for year in sorted(letters_by_year.keys()):
content.append(f"### Année {year}")
content.append("")
content.append("| Date | Lieu d'époque | Lieu actuel | Bateau | Transcription | Images | Pages |")
content.append("|------|---------------|-------------|---------|---------------|---------|-------|")
for date, info in letters_by_year[year]:
transcription_link = f"[📝](transcriptions/{date}.md)" if info['has_transcription'] else ""
images_link = generate_image_links(info)
lieu_epoque = format_location_info(info)
lieu_actuel = format_current_location(info)
bateau = info['bateau'] if info['bateau'] else "-"
content.append(f"| {date} | {lieu_epoque} | {lieu_actuel} | {bateau} | {transcription_link} | {images_link} | {info['image_count']} |")
content.append("")
# Informations sur les personnages
content.append("## Personnages principaux")
content.append("")
content.append("- **Jules Berton** - Officier de marine, auteur principal des lettres")
content.append("- **Marie** - Sœur de Jules, destinataire fréquente")
content.append("- **Henriette** - Sœur")
content.append("- **Camille** - Beau-frère")
content.append("- **M. de Brazza** - Administrateur colonial mentionné")
content.append("")
# Thèmes
content.append("## Thèmes récurrents")
content.append("")
content.append("- **Correspondance familiale** - Échanges entre frères et sœurs")
content.append("- **Carrière navale** - Vie d'officier de marine, embarquements, navigations")
content.append("- **Missions coloniales** - Service au Gabon, en Afrique équatoriale")
content.append("- **Deuil familial** - Mort du père en 1886")
content.append("- **Vie sociale** - Relations avec les familles et amis")
content.append("")
# Notes techniques
content.append("## Notes techniques")
content.append("")
content.append("- Les transcriptions respectent l'orthographe et la ponctuation originales")
content.append("- Les lettres sont organisées par ordre chronologique")
content.append("- Les métadonnées incluent les lieux d'époque et actuels quand disponibles")
content.append("- Les images sont numérisées en haute résolution au format JPG")
content.append("- Certaines lettres peuvent avoir plusieurs versions (suffixe -2)")
content.append("")
content.append("---")
content.append("")
content.append(f"*Dernière mise à jour : {datetime.now().strftime('%d %B %Y')}*")
content.append(f"*Généré automatiquement à partir des métadonnées Excel*")
return "\n".join(content)
def main():
"""Fonction principale."""
base_path = Path(__file__).parent
json_path = base_path / "lettres_metadata.json"
readme_path = base_path / "README.md"
print("📚 GÉNÉRATION DU README.MD AVEC MÉTADONNÉES")
print("=" * 50)
# Charger les métadonnées
metadata = load_metadata(json_path)
if not metadata:
return
print(f"{len(metadata)} entrées chargées depuis le JSON")
# Grouper par date
letters_by_date = group_letters_by_date(metadata)
# Consolider les informations par lettre
letters_data = {}
for date, entries in letters_by_date.items():
letters_data[date] = get_letter_info(entries)
print(f"{len(letters_data)} lettres consolidées")
# Générer les statistiques
stats = generate_statistics(letters_data)
print(f"✓ Statistiques générées")
print(f" - {stats['total_letters']} lettres")
print(f" - {stats['total_pages']} pages")
print(f" - {len(stats['years'])} années couvertes")
print(f" - {len(stats['locations'])} lieux différents")
print(f" - {len(stats['boats'])} navires mentionnés")
# Générer le contenu du README
readme_content = generate_readme_content(letters_data, stats)
# Écrire le fichier
try:
with open(readme_path, 'w', encoding='utf-8') as f:
f.write(readme_content)
print(f"✓ README.md généré avec succès : {readme_path}")
except Exception as e:
print(f"❌ Erreur lors de l'écriture : {e}")
if __name__ == "__main__":
main()