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.
This commit is contained in:
371
generate_readme.py
Normal file
371
generate_readme.py
Normal file
@@ -0,0 +1,371 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user