Introduction

Je voulais convertir ma base Heredis 2020 vers Gramps. Voici mes notes sur ce chantier.

Gramps est un logiciel de généalogie libre et open source.

Avertissement : cette page est en cours de rédaction.

Avant de convertir

J'ai d'abord essayé d'exporter en GEDCOM, option disponible dans Heredis. Résultat : le fichier n'était pas compatible avec Gramps (données perdues, balises inconnues…).

J'ai trouvé une discussion sur un forum de généalogie français où quelqu'un avait réussi, sans partager son code. Il explique que la base Heredis est une base SQLite.

Alors : écrivons le code !

Code

Introduction

J'utilise Python car, mis à part du scripting rapide, je n'ai jamais fait de projet complet dans ce langage.

J'essayerai de poster le code sur Github, mais pour l'instant il contient trop de données perso...

Lire la base SQLite

#!/usr/bin/python3
import sys, getopt, sqlite3

con = sqlite3.connect('heredis.db')
cur = con.cursor()
for row in cur.execute("SELECT * FROM table"):
    print(row)

Schéma de la base Heredis

J'ai utilisé dbdiagram.io pour visualiser le schéma.

Schéma de la base Heredis

Architecture de la solution

Après avoir analysé le schéma SQLite d'Heredis, j'ai structuré le projet en modules Python pour séparer la lecture de la base, la transformation des données et la communication avec Gramps.

Structure du projet

heredis-to-gramp/
├── heredis/        # Accès et utilitaires Heredis
├── gramps/         # Modèles et client Gramps Web
├── utils/          # Outils (géocodage, mapping, affichage)
├── commands.py
├── HeredisToGramps.py
└── main.py

Connexion à la base Heredis

La première étape reste l'extraction du fichier SQLite depuis l'archive .hmw :

cp mon-fichier.hmw/mon-fichier.heredis ./heredis.db

(faites des sauvegardes !)

J'utilise une classe singleton HeredisDb pour réutiliser une seule connexion SQLite et éviter les ouvertures/fermetures répétées.

Comprendre le schéma

Le schéma tourne autour de la table centrale Individus. Les autres tables clés sont : Evts, LienIndividuEvenement, Lieux, Sources, Medias, Unions.

Certaines spécificités d'Heredis demandent des adaptations : prénoms multiples, professions hiérarchiques, témoins d'événements, numérotation Sosa, etc.

Mapping vers Gramps

Le mapping documente la correspondance entre types d'événements, genres, lieux et les objets Gramps. Un fichier de mapping (heredis_gramps_mapping.txt) centralise ces correspondances.

Intégration avec Gramps Web

Pour l'import, j'utilise l'API REST de Gramps Web plutôt que l'interface CLI. Un service Docker Compose facilite le déploiement local de Gramps Web.

Au début, j'avais envisagé d'utiliser la CLI de Gramps, mais l'API REST s'est avérée plus flexible et avec une courbe d'apprentissage plus rapide.

Il faudrait certainement réécrire une partie du code pour utiliser la CLI, mais pour l'instant je me concentre sur l'API REST (et c'est du one-shot!).

Import par lots vs. import unitaire

J'ai expérimenté un import par lots via l'endpoint /objects pour améliorer les performances, mais l'approche s'est révélée instable dans certains cas. En production la méthode la plus fiable est l'import unitaire, même si plus lent.

Limitations et améliorations futures

  • Témoins d'événements non transférés
  • Détection et fusion des doublons
  • Export incrémental
  • Interface web de suivi
  • Rapport détaillé des éléments non convertis

Cette approche réutilise la même connexion pour toutes les requêtes, évitant ainsi le coût d'ouvertures/fermetures répétées.

Comprendre le schéma Heredis

Le schéma d'Heredis est organisé autour de la table centrale Individus. Voici les tables principales :

Principales tables

  1. Individus : personnes (noms, prénoms, genre, etc.)
  2. Evenements : naissances, décès, mariages, etc.
  3. LienIndividuEvenement : liaison personnes ↔ événements
  4. Lieux : emplacements géographiques
  5. Sources : sources documentaires
  6. Medias : fichiers médias (photos, documents, etc.)
  7. IndividusUnions : mariages / unions

Spécificités d'Heredis

Quelques particularités à prendre en compte :

  • Prénoms multiples : table Prenoms avec notion de "prénom usuel"
  • Professions multiples : table Professions avec hiérarchie
  • Témoins d'événements : gérés via LienIndividuEvenement
  • Qualité des sources : plusieurs niveaux (Source, Information, Preuve)
  • Numérotation Sosa : table dédiée à la numérotation d'Aboville

Différences structurelles

Certaines notions n'ont pas d'équivalent direct dans Gramps :

  • professions multiples → transformées en attributs multiples
  • prénoms multiples → stockés dans name.first_name
  • témoins d'événements → non pris en charge nativement (peuvent devenir des notes)
  • subdivisions géographiques → converties en Place avec coordonnées

Création d'objets via l'API

Le projet contient des modèles pour représenter les objets Gramps côté client. Exemple simplifié pour créer une personne :

class GrampsPeople(GrampsObject):
    def __init__(self, prenoms: str = "", nom: str = ""):
        self.name = f"{prenoms} {nom}".strip()
        self.primary_name = GrampsName()
        self.primary_name.first_name = prenoms
        self.primary_name.surname_list = [GrampsSurname(nom, primary=True)]
        self.primary_name.type = "Birth Name"
        self.gender = 2  # Inconnu par défaut

    def set_gender(self, heredis_gender: int):
        # Heredis : 109 = Masculin, 102 = Féminin
        self.gender = 1 if heredis_gender == 109 else 0 if heredis_gender == 102 else 2
    
    def to_dict(self):
        return {
            "primary_name": self.primary_name.to_dict(),
            "gender": self.gender,
        }

Utilisation du script

Le script principal propose plusieurs modes d'utilisation :

Statistiques de la base Heredis

Parce que c'est toujours intéressant de connaître la taille de sa base avant de lancer un import !

python3 main.py --stats

# Exemple de sortie
Nombre d'individus : 1245
Nombre d'événements : 3892

Rechercher un individu

Le programme permet de rechercher des individus par nom et d'afficher leurs dates et relations :

python3 main.py --search "Dupont" --show-dates --show-parents

Exemple d'affichage :

👤 Jean DUPONT (ID : 42)
    Sexe : Masculin
    📅 Naissance : 1890-03-15 à Paris
    📅 Décès : 1965-07-22 à Lyon
    👨‍👩‍👧 Parents :
        - Pierre DUPONT (ID : 21)
        - Marie MARTIN (ID : 22)

👤 Sophie DUPONT (ID : 156)
    Sexe : Féminin
    📅 Naissance : 1920-05-10 à Marseille

On peut aussi interroger un individu par son identifiant :

python3 main.py --individual --id 42 --show-dates --show-parents

Format d'affichage (exemple) :

42	DUPONT	Jean (1890-03-15 - 1965-07-22) [M]

Opérations d'import

Le programme supporte l'import individuel et l'import en lot (expérimental) :

# Importer un individu
python3 main.py --import-individual --id 42 -y

# Importer avec parents et enfants
python3 main.py --import-individual --id 42 --import-parents --import-children -y

# Importer tous les individus (avec limite)
python3 main.py --import-all --limit 100 -y

L'option -y confirme automatiquement les opérations sans demander de saisie.

Géocodage des lieux

Gramps peut stocker des coordonnées GPS pour les lieux. J'ai ajouté un géocodage automatique :

import requests
from functools import lru_cache

@lru_cache(maxsize=1000)
def geocode_place(place_name):
    """Géocode un lieu et met le résultat en cache."""
    response = requests.get(
        "https://nominatim.openstreetmap.org/search",
        params={
            "q": place_name,
            "format": "json",
            "limit": 1
        }
    )
    
    if response.json():
        result = response.json()[0]
        return {
            "lat": result["lat"],
            "lon": result["lon"]
        }
    return None

Un cache mémoire permet d'éviter les requêtes répétées pour les mêmes lieux, améliorant ainsi les performances (et en limitant les appels à l'API de Nominatim).

Gestion des médias

Les médias dans Heredis sont référencés par des chemins absolus : il faut copier les fichiers et adapter les chemins pour Gramps.

def get_media_for_individual(self, individual_id):
    """
    Récupère tous les médias liés à un individu.

    Retourne une liste de dictionnaires contenant :
      - media_id (int)
      - file_path (str)
      - file_name (str)
      - media_principal (bool)
    """
    query = """
        SELECT 
            m.CodeID as media_id,
            m.FilePath as file_path,
            m.FileName as file_name,
            lmi.MediaPrincipal as media_principal
        FROM Medias m
        JOIN LiensMediaIndividu lmi ON m.CodeID = lmi.XrefMedia
        WHERE lmi.XrefIndividu = ?
        ORDER BY lmi.Ordre
    """
    # Exécuter la requête et retourner les résultats

Import par lots — expérimentations

J'ai initialement développé un import par lots via l'endpoint /objects de l'API Gramps pour améliorer les performances (réduire les 4–5 appels API par personne à 1–2 appels pour plusieurs personnes).

Cependant, cette approche par lots a été abandonnée à cause d'instabilités : échecs de transaction et comportements incohérents avec certains jeux de données.

La méthode stable actuelle importe les individus un par un — plus fiable, mais plus lent.

Limitations actuelles et améliorations futures

Limitations actuelles

  1. Témoins d'événements : non transférés (pas d'équivalent natif dans Gramps)
  2. Hiérarchie des professions : aplatie en liste simple
  3. Recadrage des photos : informations perdues
  4. Tâches Heredis : non exportées
  5. Vitesse d'import : l'import unitaire est plus lent mais plus stable

Améliorations envisagées

  • Exporter les témoins comme notes textuelles
  • Détection et fusion des doublons
  • Export incrémental
  • Interface web de suivi de l'import
  • Validation des données avant import
  • Rapport détaillé des éléments non convertis
  • Implémentation stable d'un import par lots

Conclusion

Ce projet m'a permis de :

  1. Comprendre en profondeur les deux modèles (Heredis et Gramps)
  2. Progresser en Python (singletons, décorateurs, optimisation SQLite)
  3. Rédiger une documentation utile
  4. Construire un convertisseur fonctionnel malgré les différences structurelles

Le code est disponible sur GitHub (lien à ajouter).

Si vous avez des questions ou des suggestions d'amélioration, ouvrez une issue !

Ressources utiles